From 5346073135945bf970c3e6a84b5c5e5b5d112eb7 Mon Sep 17 00:00:00 2001 From: Dave Ward Date: Sat, 21 May 2011 10:41:57 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20V3.4-BUG-FIX=20to=20HEAD=20=20=20=2025?= =?UTF-8?q?384:=20ALF-5352=20-=20user=20usages=20=20=20=20-=20re-apply=20r?= =?UTF-8?q?19862=20(ALF-713=20fix)=20that=20was=20lost=20(probably=20due?= =?UTF-8?q?=20to=20merge=20conflict)=20=20=20=2025440:=20Merged=20DEV/TEMP?= =?UTF-8?q?ORARY=20to=20V3.4-BUG-FIX=20=20=20=20=20=20=2025232:=20ALF-4300?= =?UTF-8?q?:=20DB2:=20Review=20schema=20(eg.=20VARCHAR=20columns)=20with?= =?UTF-8?q?=20respect=20to=20multi-byte=20support=20(when=20using=20DB2=20?= =?UTF-8?q?/=20UTF-8)=20=20=20=20=20=20=2025371:=20ALF-4300:=20DB2:=20Revi?= =?UTF-8?q?ew=20schema=20(eg.=20VARCHAR=20columns)=20with=20respect=20to?= =?UTF-8?q?=20multi-byte=20support=20(when=20using=20DB2=20/=20UTF-8)=20?= =?UTF-8?q?=20=20=20=20=20=20-=20All=20VARCHAR=20fields=20quadrupled=20for?= =?UTF-8?q?=20DB2=20relative=20to=20MySQL=20to=20support=20UTF-8=20charact?= =?UTF-8?q?er=20sets=20=20=20=20=20=20=20-=20=E2=80=98varchar-field-sizes-?= =?UTF-8?q?quadruple-increasing.sql=E2=80=99=20introduced=20by=20patch=20?= =?UTF-8?q?=20=20=20=20=20=20-=20Minor=20conflict=20anticipated=20on=20Alf?= =?UTF-8?q?rescoSchemaUpdate-2.1-A--to--2.2-ACL.sql;=20ensure=20larger=20c?= =?UTF-8?q?olumn=20size=20is=20kept.=20=20=20=20=20=20=20-=20Schema=20numb?= =?UTF-8?q?er=20now=20at=204201.=20=20=20=2025444:=20Fix=20ALF-6689:=20WQS?= =?UTF-8?q?:=20Incorrect=20work=20of=20email=20field=20on=20contact=20page?= =?UTF-8?q?=20=20=20=20Fix=20ALF-7058:=20WQS:=20Blog=20article=20page=20of?= =?UTF-8?q?=20custom=20type=20gives=20an=20error=20=20=20=2025447:=20Fix?= =?UTF-8?q?=20ALF-5198:=20WQS:=20Error=20while=20Name=20field=20filled=20w?= =?UTF-8?q?ith=20more=20than=2070=20characters=20or=20special=20characters?= =?UTF-8?q?=20=20=20=2025506:=20ALF-6281:=20Actions=20for=20folder=20do=20?= =?UTF-8?q?not=20work=20(Quickr=20connector=20plug-in)=20=20=20=2025534:?= =?UTF-8?q?=20ALF-646:=20Alfresco=20Logo=20isn't=20displayed=20in=20SPP=20?= =?UTF-8?q?Open=20window=20=20=20=2025590:=20Merged=20DEV/TEMPORARY=20to?= =?UTF-8?q?=20V3.4-BUG-FIX=20=20=20=20=20=20=2025582:=20ALF-6282:=20Incorr?= =?UTF-8?q?ect=20behavior=20of=20Propertiesa=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?-=20Add=20"Created",=20"Modified"=20and=20"Label"=20tags=20to?= =?UTF-8?q?=20document/folder=20entries=20in=20AlfrescoAtomBasedFeedServic?= =?UTF-8?q?eImpl.createEntry()=20=20=20=2025595:=20Merged=20DEV/TEMPORARY?= =?UTF-8?q?=20to=20V3.4-BUG-FIX=20=20=20=20=20=20=2025592:ALF-7194:=20Savi?= =?UTF-8?q?ng=20a=20Excel=20file=20with=20CIFS=20reset=20permissions.=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20-=20Copy=20value=20of=20"Inherit=20P?= =?UTF-8?q?arent=20Space=20Permissions"=20flag=20to=20the=20new=20node=20i?= =?UTF-8?q?n=20ContentDiskDriver.cloneNode()=20method.=20=20=20=2025635:?= =?UTF-8?q?=20Merged=20DEV/TEMPORARY=20to=20V3.4-BUG-FIX=20=20=20=20=20=20?= =?UTF-8?q?=2025574:=20ALF-6288:=20Action=20'Send=20Link'=20is=20not=20wor?= =?UTF-8?q?ked=20=20=20=20=20=20=2025616:=20ALF-6288:=20Action=20'Send=20L?= =?UTF-8?q?ink'=20is=20not=20worked=20=20=20=20=20=20=20=20=20=20-=20Alfre?= =?UTF-8?q?scoQuickrPathHelper.getNodePath()=20method=20was=20modified=20t?= =?UTF-8?q?o=20return=20full=20node=20path,=20e.g.=20"/Company=20Home/Site?= =?UTF-8?q?s/TestSite/documentLibrary/test.odt".=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20-=20AlfrescoQuickrPathHelper.removeSlashesAndRoot(String?= =?UTF-8?q?=20value)=20method=20was=20renamed=20to=20AlfrescoQuickrPathHel?= =?UTF-8?q?per.resolveNodePath(String=20path,=20boolean=20isRelative).=20?= =?UTF-8?q?=20=20=2025638:=20Merged=20DEV/TEMPORARY=20to=20V3.4-BUG-FIX=20?= =?UTF-8?q?(with=20feedback=20from=20Gary)=20=20=20=20=20=20=2025376:=20AL?= =?UTF-8?q?F-684:=20Ftp=20requests=20to=20the=20IPv6=20resolved=20hostname?= =?UTF-8?q?=20are=20failing=20=20=20=20=20=20=20=20=20=20The=20ftp.ipv6.en?= =?UTF-8?q?abled=20property=20was=20removed,=20and=20a=20ServerSocket=20is?= =?UTF-8?q?=20opened=20without=20InetAddress=20parameter.=20It=20allows=20?= =?UTF-8?q?Java=20to=20determine=20if=20a=20IPv6=20is=20used=20and=20bind?= =?UTF-8?q?=20"::"=20any=20local=20address=20to=20the=20server=20socket.?= =?UTF-8?q?=20It=20allows=20to=20connect=20to=20the=20Alfresco=20FTP=20usi?= =?UTF-8?q?ng=20both=20IPv4=20and=20IPv6=20addresses=20regardless=20to=20s?= =?UTF-8?q?erver=20OS.=20=20=20=2025639:=20ALF-5115:=20Removed=20circular?= =?UTF-8?q?=20dependency=20from=20non-continuous=20builds=20=20=20=20-=20N?= =?UTF-8?q?o=20need=20to=20use=20-f=20continuous.xml=20anymore=20when=20bu?= =?UTF-8?q?ilding=20enterprise=20or=20community=20=20=20=20-=20Added=20ass?= =?UTF-8?q?emble-command-extras=20stub=20to=20community=20build.xml,=20ove?= =?UTF-8?q?rridden=20by=20enterprise=20build.xml=20and=20called=20by=20con?= =?UTF-8?q?tinuous.xml=20=20=20=2025699:=20MERGE=20DEV=20to=20V3.4-BUG-FIX?= =?UTF-8?q?=20=20=20=20=20=20ALF-5745=20:=20AVMTemplateNode=20d:date=20pro?= =?UTF-8?q?perties=20are=20wrong.=20=20=20=2025770:=20Investigation=20test?= =?UTF-8?q?s=20for=20ALF-6904:=20Wrong=20behaviour=20when=20overriding=20a?= =?UTF-8?q?=20constraint=20in=20content=20model=20=20=20=2025774:=20Fixed?= =?UTF-8?q?=20ALF-7193:=20XAM=20Connector:=20xam.archive.nodePropertiesToW?= =?UTF-8?q?rite=20must=20not=20make=20properties=20mandatory=20=20=20=20?= =?UTF-8?q?=20-=20The=20property=20had=20to=20be=20set=20to=20null=20-=20a?= =?UTF-8?q?chievable=20because=20it=20is=20MLText=20=20=20=20=20-=20Added?= =?UTF-8?q?=20full=20d:mltext=20expansion=20=20=20=20=20-=20Added=20double?= =?UTF-8?q?=20checks=20for=20nulls=20creeping=20in=20=20=20=2025851:=20Fix?= =?UTF-8?q?ed=20ALF-7381:=20OOM=20when=20Debug=20logging=20on=20ReferenceC?= =?UTF-8?q?ountingReadOnlyIndexReaderFactory=20=20=20=20=20-=20Use=20a=20W?= =?UTF-8?q?eakHashMap=20=20=20=2025853:=20Merged=20DEV/TEMPORARY=20to=20V3?= =?UTF-8?q?.4-BUG-FIX=20=20=20=20=20=20=2025852:=20ALF-6373:=20Preview=20i?= =?UTF-8?q?sn't=20generated=20for=20content=20added=20by=20quickr=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20Added=20AlfrescoQuickrDocumentHelper.getMi?= =?UTF-8?q?meType(NodeRef=20fileRef)=20method=20that=20resolves=20the=20mi?= =?UTF-8?q?me=20type=20using=20the=20node=20name.=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20Set=20the=20mime=20type=20of=20nodes=20created=20in=20Alf?= =?UTF-8?q?rescoDocumentServiceImpl=20and=20AlfrescoAtomBasedFeedServiceIm?= =?UTF-8?q?pl.=20=20=20=2025860:=20Resolve=20ALF-7286:=20CMIS=20UP=20link?= =?UTF-8?q?=20for=20document=20doesn't=20contain=20all=20parents=20=20=20?= =?UTF-8?q?=2025866:=20Fix=20for=20ALF-6312=20=20=20=20=20=20-=20moved=20c?= =?UTF-8?q?onfiguration=20to=20the=20correct=20file=20to=20be=20picked=20u?= =?UTF-8?q?p=20by=20Spring=20Surf=20=20=20=2025870:=20Merged=20DEV/TEMPORA?= =?UTF-8?q?RY=20to=20V3.4-BUG-FIX=20=20=20=20=20=20=2025840:=20ALF-6279:?= =?UTF-8?q?=20Failed=20to=20save=20properties=20in=20Symphony=20documents.?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20AlfrescoDocumentServiceImpl.setDo?= =?UTF-8?q?cType()=20method=20was=20modified=20to=20check=20for=20existenc?= =?UTF-8?q?e=20of=20document=20type=20in=20provided=20ClbDraft=20object.?= =?UTF-8?q?=20=20=20=2025908:=20Set=203.4.2=20revision=20=20=20=2025931:?= =?UTF-8?q?=20Fix=20for=20ALF-6565:=20Explorer=20UI=20allows=20duplication?= =?UTF-8?q?=20of=20Replication=20targets=20which=20in=20effects=20breaks?= =?UTF-8?q?=20Alfresco=20=20=20=2025987:=20DOC-238:=20Sharepoint=20(VTI)?= =?UTF-8?q?=20Protocol=20Configuration=20Documentation=20is=20outdated=20-?= =?UTF-8?q?=20have=20added=20comments=20in=20the=20properties=20file=20to?= =?UTF-8?q?=20indicate=20what=20they=20do=20=20=20=2025994:=20WQS:=20Added?= =?UTF-8?q?=20a=20little=20more=20information=20to=20the=20log=20message?= =?UTF-8?q?=20when=20a=20404=20is=20returned=20=20=20=2026003:=20ALF-7663:?= =?UTF-8?q?=20NPEs=20when=20using=20PermissionService=20with=20AVM=20store?= =?UTF-8?q?s=20=20=20=20-=20added=20unit=20tests=20for=20regression=20test?= =?UTF-8?q?ing=20=20=20=20-=20no=20longer=20an=20issue=20=20=20=2026004:?= =?UTF-8?q?=20ALF-7662:=20AVM=20permissions=20-=20access=20denied=20except?= =?UTF-8?q?ion=20when=20trying=20to=20create=20file=20in=20a=20layered=20d?= =?UTF-8?q?irectory,=20when=20no=20explicit=20permissions=20set=20(on=20ro?= =?UTF-8?q?ot=20dir=20node)=20=20=20=20-=20test=20passes=20on=203.4.2=20-?= =?UTF-8?q?=20updated=20test=20to=20reflect=202.1=20state=20=20=20=2026011?= =?UTF-8?q?:=20ALF-6372=20Now=20when=20the=20package=20manager=20adds=20it?= =?UTF-8?q?ems=20to=20a=20package=20it=20does=20not=20mark=20the=20items?= =?UTF-8?q?=20as=20modified.=20=20=20=2026026:=20ALF-6350:=20Add=20support?= =?UTF-8?q?=20for=20Ideographic=20Space=20to=20the=20Full=20Text=20Search?= =?UTF-8?q?=20-=20=20=20=20-=20added=20full=20set=20of=20Unicode=20charact?= =?UTF-8?q?ers=20for=20letters=20and=20digits=20(0x0000=20-=200xFFFF)=20?= =?UTF-8?q?=20=20=20-=20added=20fill=20set=20of=20Unicode=20whitespace=20?= =?UTF-8?q?=20=20=20-=20class=20to=20generate=20Unicode=20types=20->=20ran?= =?UTF-8?q?ges=20=20=20=20-=20upgraded=20antlr=20to=203.3.=20(required=20t?= =?UTF-8?q?o=20resolve=203.2=20bug)=20=20=20=20-=20fixed=20Lexer=20and=20p?= =?UTF-8?q?arser=20test=20=20=20=20-=20much=20still=20depends=20on=20the?= =?UTF-8?q?=20tokeniser=20....=20=20=20=2026048:=20Fix=20for=20ALF-7507=20?= =?UTF-8?q?-=20Manage=20Deleted=20Items=20-=20Recover=20All=20Icon=20Missi?= =?UTF-8?q?ng=20=20=20=2026050:=20Fixed=20failing=20tests=20WorkflowFormPr?= =?UTF-8?q?ocessorTest=20and=20TaskFormProcessorTest.=20=20=20=2026068:=20?= =?UTF-8?q?Resolve=20ALF-7342=20-=20dynamic=20Models=20-=20unable=20to=20a?= =?UTF-8?q?dd=20new=20properties=20to=20an=20aspect=20if=20the=20aspect=20?= =?UTF-8?q?is=20a=20mandatory=20aspect=20of=20another=20aspect=20=20=20=20?= =?UTF-8?q?26082:=20WQS:=20Improved=20debug-level=20logging=20to=20track?= =?UTF-8?q?=20WQS-to-repo=20interaction=20better.=20=20=20=2026094:=20Some?= =?UTF-8?q?=20formatting=20=20=20=2026095:=20Fixed=20ALF-7531:=20alfresco.?= =?UTF-8?q?jgroups.bind=5Finterface=20is=20not=20taken=20into=20account=20?= =?UTF-8?q?=20=20=20=20-=20Added=20'bind=5Finterface'=20property=20to=20TC?= =?UTF-8?q?P=20config=20=20=20=20=20-=20Value=20injected=20into=20system?= =?UTF-8?q?=20properties:=20alfresco.jgroups.bind=5Finterface=20=20=20=202?= =?UTF-8?q?6130:=20Fix=20for=20CIFS=20multitenancy=20not=20working.=20ALF-?= =?UTF-8?q?6816.=20=20=20=20Need=20to=20search=20the=20global=20share=20li?= =?UTF-8?q?st=20for=20admin=20named=20pipe=20shares.=20=20=20=2026131:=20F?= =?UTF-8?q?ix=20for=20CIFS=20cut/paste=20write-protected=20error.=20ALF-18?= =?UTF-8?q?22=20=20=20=2026132:=20Solairs/NFS=20ReadDir=20issue,=20cannot?= =?UTF-8?q?=20list=20folder=20more=20than=20once.=20ALF-5386=20=20=20=20Se?= =?UTF-8?q?arch=20resume=20id/cookie=20value=20of=20zero=20has=20special?= =?UTF-8?q?=20meaning=20for=20NFS.=20=20=20=2026133:=20Changed=20FTP=20ret?= =?UTF-8?q?urn=20status=20for=20MKD=20command=20to=20257,=20for=20RFC=20co?= =?UTF-8?q?mpliance.=20ALF-7501.=20=20=20=2026136:=20Fix=20for=20FTP=20CWD?= =?UTF-8?q?-MKD-CWD=20sequence=20failing.=20ALF-7530=20=20=20=20Trim=20fil?= =?UTF-8?q?esystem=20path=20to=20make=20sure=20there=20is=20no=20trailing?= =?UTF-8?q?=20backslash.=20=20=20=2026144:=20Merged=20V3.4=20to=20V3.4-BUG?= =?UTF-8?q?-FIX=20=20=20=20=20=20=2025890:=20Fix=20for=20ALF-5796=20-=20It?= =?UTF-8?q?'s=20impossible=20to=20add=20tag=20in=20Japanese=20language=20(?= =?UTF-8?q?IE=20specific)=20=20=20=20=20=20=2025891:=20Fixes:=20ALF-7363?= =?UTF-8?q?=20(updated=20JA=20translation)=20=20=20=20=20=20=2025894:=20Fi?= =?UTF-8?q?xed=20Legal=20and=20License=20url=20in=20About=20dialog=20=20?= =?UTF-8?q?=20=20=20=20=2025896:=20ALF-6476=20-translation=20on=20Transfer?= =?UTF-8?q?=20Target=20configuration=20=20=20=20=20=20=2025899:=20First=20?= =?UTF-8?q?cut=20of=20French=20transfer=20properties.=20=20Mostly=20Englis?= =?UTF-8?q?h!=20but=20has=203=20lines=20of=20French.=20=20=20=20=20=20=202?= =?UTF-8?q?5900:=20Fix=20for=20ALF-6916:=20More=20Deployment=20Reports=20a?= =?UTF-8?q?ction=20causes=20an=20error=20=20=20=20=20=20=2025901:=20Fixes:?= =?UTF-8?q?=20ALF-6847=20(Italian=20Translation)=20=20=20=20=20=20=2025902?= =?UTF-8?q?:=20Fixes:=20ALF-6861=20(incorrectly=20encoded=20German=20chara?= =?UTF-8?q?cter)=20=20=20=20=20=20=2025904:=20Fixes:=20ALF-6755=20(transla?= =?UTF-8?q?tion=20quoting=20error)=20=20=20=20=20=20=2025911:=20Fixes:=20A?= =?UTF-8?q?LF-6478=20-=20French=20translation=20correction=20=20=20=20=20?= =?UTF-8?q?=20=2025913:=20Fixes:=20ALF-6334=20and=20ALF-6477=20(Profile=20?= =?UTF-8?q?Edit=20style=20issues=20-=20makes=20mark=20up=20consistent,=20c?= =?UTF-8?q?lears=20floats=20to=20allow=20for=20foreign=20languages=20being?= =?UTF-8?q?=20a=20tad=20more=20verbose=20than=20English=20and=20adjusts=20?= =?UTF-8?q?padding=20to=20help=20input=20boxes=20line=20up.=20=20=20=20=20?= =?UTF-8?q?=20=2025914:=20Help=20URL=20for=203.4=20enterprise=20docs=20=20?= =?UTF-8?q?=20=20=20=20=2025915:=20Updates=20TinyMCE=20translation=20as=20?= =?UTF-8?q?requested=20in=20ALF-6486.=20=20=20=20=20=20=2025916:=20Updated?= =?UTF-8?q?=20support=20URL=20in=20readme=20=20=20=20=20=20=2025917:=20Fix?= =?UTF-8?q?es:=20ALF-6482,=20wrong=20word=20order=20in=20FR=20has=20been?= =?UTF-8?q?=20corrected.=20=20=20=20=20=20=2025919:=20Fixes:=20ALF-6655=20?= =?UTF-8?q?updated=20to=20allow=20enough=20space=20for=20verbose=20languag?= =?UTF-8?q?es=20(e.g.=20FR,=20ES)=20=20=20=20=20=20=2025925:=20Fix=20for?= =?UTF-8?q?=20ALF-6885:=20alfresco-enterprise-3.4.0.zip=20package=20has=20?= =?UTF-8?q?2=20issues=20with=20apply=5Famps.sh=20=20=20=20=20=20=2025936:?= =?UTF-8?q?=20ALF-6469=20-=20transfer=20folders=20internationalized=20=20?= =?UTF-8?q?=20=20=20=20=2025941:=20Merged=20BRANCHES/DEV/dwebster/=20to=20?= =?UTF-8?q?BRANCHES/V3.4:=20=20=20=20=20=20=20=20=20=2025939:=20Latest=20J?= =?UTF-8?q?A=20update=20from=20Translators,=20received:=202011-02-28=20=20?= =?UTF-8?q?=20=20=20=20=2025943:=20Merged=20BRANCHES/DEV/dwebster/=20to=20?= =?UTF-8?q?BRANCHES/V3.4:=20=20=20=20=20=20=20=20=20=2025942:=20Language?= =?UTF-8?q?=20updates=20for=20property=20string=20changes=20and=20addition?= =?UTF-8?q?s=20between=203.4.0=20and=203.4.1=20=20=20=20=20=20=2025946:=20?= =?UTF-8?q?ALF-7191:=20Generate=20duplicate=20=5Fen=20resource=20bundles,?= =?UTF-8?q?=20using=20location=20of=20=5Ffr=20bundles=20as=20a=20guide=20f?= =?UTF-8?q?or=20ALL=20bundles=20under=20alfresco=20and=20share=20WEB-INF/c?= =?UTF-8?q?lasses=20=20=20=20=20=20=2025947:=20ALF-7191:=20Reverse=20accid?= =?UTF-8?q?entally=20committed=20changes=20to=20WebDAV=20in=2025946!=20=20?= =?UTF-8?q?=20=20=20=20=2025949:=20Fixes:=20ALF-6521=20and=20ALF-6493=20(S?= =?UTF-8?q?ite=20discussion's=20handling=20of=20tags=20with=20special=20ch?= =?UTF-8?q?aracters=20in=20them)=20=20=20=20=20=20=2025950:=20Fixes:=20ALF?= =?UTF-8?q?-6489=20-=20encodes=20the=20content's=20name=20to=20make=20it?= =?UTF-8?q?=20safe=20for=20a=20URL.=20=20=20=20=20=20=2025951:=20Fixes:=20?= =?UTF-8?q?ALF-6487=20Adjusts=20styling=20for=20rename=20panel=20=20=20=20?= =?UTF-8?q?=20=20=2025954:=20ALF-7191=20-=20Generate=20=5Fen=20bundles=20f?= =?UTF-8?q?or=20all=20message=20bundles=20for=20Explorer=20and=20Share=20-?= =?UTF-8?q?=20using=20known=20location=20of=20message=20bundles=20=20=20?= =?UTF-8?q?=20=20=20=2025956:=20Allow=20a=20bit=20of=20leeway=20with=20aud?= =?UTF-8?q?it=20timing=20during=20delete=20tests=20=20=20=20=20=20=2025957?= =?UTF-8?q?:=20Fixed=20ALF-7341:=20Upload=20performance=20degradation=20wh?= =?UTF-8?q?en=20uploading=20contents=20to=20folders=20with=20content=20rul?= =?UTF-8?q?es=20applied.=20=20=20=20=20=20=20=20=20=20-=20Action=20executi?= =?UTF-8?q?ons=20were=20being=20recorded=20by=20the=20ActionTrackingServic?= =?UTF-8?q?e=20for=20all=20actions=20=20=20=20=20=20=20=20=20=20-=20Post-c?= =?UTF-8?q?ommit=20updates=20of=20the=20action=20node=20was=20reducing=20p?= =?UTF-8?q?erformance=20=20=20=20=20=20=20=20=20=20-=20Added=20'trackStatu?= =?UTF-8?q?s'=20to=20ActionExecuter,=20ActionDefinition=20and=20Action=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20-=20Default=20'trackStatus'=20is=20f?= =?UTF-8?q?alse;=20exceptions:=20'replicationActionExecutor'=20and=20'comm?= =?UTF-8?q?it-transfer'=20=20=20=20=20=20=20=20=20=20-=20Adjusted=20tests?= =?UTF-8?q?=20accordingly=20=20=20=20=20=20=20=20=20=20-=20General=20clean?= =?UTF-8?q?up=20around=20modifications=20=20=20=20=20=20=2025964:=20ALF-56?= =?UTF-8?q?25=20-=20When=20viewing=20properties=20in=20version=20history?= =?UTF-8?q?=20hitting=20close=20results=20in=20loop=20=20=20=20=20=20=2025?= =?UTF-8?q?970:=20Merged=20BRANCHES/DEV/V3.4-BUG-FIX=20to=20BRANCHES/V3.4:?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=2025967:=20Fix=20ALF-7440:=20WQS:?= =?UTF-8?q?=20commons-pool=20library=20has=20been=20upgraded,=20but=20WQS?= =?UTF-8?q?=20build=20properties=20have=20not=20been=20changed=20according?= =?UTF-8?q?ly=20=20=20=20=20=20=2025971:=20ALF-7441:=20Help=20URLs=20point?= =?UTF-8?q?ing=20to=20new=20doc=20system=20(1=20of=202)=20=20=20=20=20=20?= =?UTF-8?q?=2025972:=20ALF-7441:=20Help=20URLs=20pointing=20to=20new=20doc?= =?UTF-8?q?=20system=20(2=20of=202)=20=20=20=20=20=20=2025974:=20Reverted?= =?UTF-8?q?=20rev=2025964=20-=20as=20fix=20is=20scheduled=20for=203.4.2=20?= =?UTF-8?q?=20=20=20=20=20=2025979:=20Disabling=20intermittent=20failing?= =?UTF-8?q?=20unit=20test.=20ALF-7443=20logged.=20=20=20=20=20=20=2025980:?= =?UTF-8?q?=20Merged=20V3.4-BUG-FIX=20to=20V3.4=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=2025978:=20ALF-7394=20-=20Alfresco=20Network=20dashlet,=20now?= =?UTF-8?q?=20Alfresco=20Support=20information=20=20=20=20=20=20=2026014:?= =?UTF-8?q?=20ALF-7087=20-=20Cannot=20override=20webscript=20files=20in=20?= =?UTF-8?q?case=20of=20using=20jboss=205.1GA=20=20=20=20=20=20=2026024:=20?= =?UTF-8?q?ALF-7466=20-=20Links=20do=20not=20appear=20correctly=20in=20the?= =?UTF-8?q?=20management=20Console=20(RM)=20=20=20=20=20=20=2026036:=20ALF?= =?UTF-8?q?-6403:=20Merged=20V3.4=20to=20V3.4=20(lost=20revision)=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2025627:=20Fixes=20ALF-7222:=20Updated=20lin?= =?UTF-8?q?ux=20installer=20window=20height=20(with=20taller=20image=20to?= =?UTF-8?q?=20hide=20additonal=20background)=20=20=20=20=20=20=2026039:=20?= =?UTF-8?q?Fix=20intermittent=20failures=20in=20InviteServiceTest.tearDown?= =?UTF-8?q?()=20=20=20=20=20=20=2026040:=20Fix=20intermittent=20failures?= =?UTF-8?q?=20in=20InviteServiceTest.setUp()=20=20=20=20=20=20=2026043:=20?= =?UTF-8?q?Sync=20up=20run-junit-test=20with=20junit=20macros=20so=20that?= =?UTF-8?q?=20it=20can=20be=20used=20to=20run=20unit=20tests=20in=20an=20e?= =?UTF-8?q?nterprise=20environment=20(e.g.=20DB2=20/=20Oracle)=20=20=20=20?= =?UTF-8?q?=20=20=2026044:=20Possibly=20fix=20intermittent=20TransferServi?= =?UTF-8?q?ceImplTest=20failures=20by=20using=20org.alfresco.repo.transact?= =?UTF-8?q?ion.RetryingTransactionInterceptor=20=20=20=20=20=20=2026052:?= =?UTF-8?q?=20Do=20not=20wait=20indefinitely=20for=20a=20heartbeat=20in=20?= =?UTF-8?q?HeartBeatTest.=20A=20broken=20heartbeat=20would=20cause=20the?= =?UTF-8?q?=20build=20to=20hang=20forever!=20=20=20=20=20=20=2026056:=20Me?= =?UTF-8?q?rged=20V3.4-TEAM=20to=20V3.4=20=20=20=20=20=20=20=20=20=2026053?= =?UTF-8?q?:=20Added=20in=20loop=20to=20wait=20for=20asynchronous=20post-f?= =?UTF-8?q?ailure=20auditing=20(ALF-3055)=20=20=20=20=20=20=2026077:=20Upp?= =?UTF-8?q?ed=20LOGFILSIZ=20to=20avoid=20failures=20in=20unit=20tests=20wi?= =?UTF-8?q?th=20large=20transactions=20on=20DB2=20=20=20=20=20=20=2026084:?= =?UTF-8?q?=20Avoid=20intermittent=20test=20failures=20in=20AbstractTestFo?= =?UTF-8?q?rmRestApi=20by=20using=20retrying=20transactions=20=20=20=20=20?= =?UTF-8?q?=20=2026096:=20Correction=20to=20DB2=20drop=20/=20create=20comm?= =?UTF-8?q?ands=20=20=20=20=20=20=2026097:=20Fixes:=20ALF-7102,=20typo=20i?= =?UTF-8?q?n=20property=20string.=20=20=20=20=20=20=2026100:=20Merged=20V3?= =?UTF-8?q?.4-TEAM=20to=20V3.4=20=20=20=20=20=20=20=20=20=2025985:=20Anoth?= =?UTF-8?q?er=20cycle=20of=20fixing=20installer=20from=20Win=20builds.=20?= =?UTF-8?q?=20Still=20waiting=20for=20official=20fix=20from=20Bitrock.=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=2026067:=20Fixes=20for=20installer=20b?= =?UTF-8?q?uilds=20on=20Windows=20=20=20=20=20=20=2026102:=20Attempt=20to?= =?UTF-8?q?=20avoid=20intermittent=20failures=20in=20TaggingServiceImplTes?= =?UTF-8?q?t=20by=20upping=20wait=20time=20=20=20=20=20=20=2026109:=20Anot?= =?UTF-8?q?her=20go=20at=20executing=20the=20db2=20creation=20statements?= =?UTF-8?q?=20synchronously=20through=20db2cmd=20=20=20=20=20=20=2026111:?= =?UTF-8?q?=20ALF-6764=20-=20Copyright=20year=20on=20Share=20login=20page?= =?UTF-8?q?=20out=20of=20date=20=20=20=20=20=20=2026127:=20Merged=20V3.4-T?= =?UTF-8?q?EAM=20to=20V3.4=20=20=20=20=20=20=20=20=20=2026120:=20Fix=20pos?= =?UTF-8?q?tgres.bki=20on=20Win=20builds=20=20=20=20=20=20=2026142:=20Merg?= =?UTF-8?q?ed=20PATCHES/V3.4.0=20to=20V3.4=20=20=20=20=20=20=20=20=20=2025?= =?UTF-8?q?999:=20ALF-7377:=20Validate=20and=20reject=20partial=20WebDAV?= =?UTF-8?q?=20requests=20sometimes=20produced=20by=20NetDrive=20=20=20=202?= =?UTF-8?q?6145:=20Merged=20V3.4=20to=20V3.4-BUG-FIX=20(RECORD=20ONLY)=20?= =?UTF-8?q?=20=20=20=20=20=2026143:=20Merged=20PATCHES/V3.4.0=20to=20V3.4?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=2026005:=20Merged=20V3.4-BUG-FIX=20?= =?UTF-8?q?to=20PATCHES/V3.4.0=20=20=20=20=20=20=20=20=20=20=20=20=2026002?= =?UTF-8?q?:=20ALF-7282=20Updated=20NodeListConverter=20so=20that=20it=20n?= =?UTF-8?q?ow=20implements=20the=20method=20revert(Object,=20ProcessDefini?= =?UTF-8?q?tion)=20=20=20=2026153:=20Fix=20for=20CIFS=20concurrent=20folde?= =?UTF-8?q?r=20listing=20returns=20wrong=20list=20of=20files.=20ALF-6385.?= =?UTF-8?q?=20=20=20=20Synchronize=20the=20VirtualCircuit.allocateSearchSl?= =?UTF-8?q?ot()=20method=20and=20put=20a=20marker=20object=20in=20the=20al?= =?UTF-8?q?located=20slot=20so=20it=20does=20not=20get=20reused=20before?= =?UTF-8?q?=20the=20real=20search=20is=20put=20into=20the=20slot.=20=20=20?= =?UTF-8?q?=2026156:=20Merged=20/BRANCHES/DEV/BELARUS/V3.3-2010=5F12=5F20?= =?UTF-8?q?=20to=20BRANCHES/DEV/V3.4-BUG-FIX:=20=20=20=20=20=20=2024902:?= =?UTF-8?q?=20ALF-5985:=20Users=20with=20an=20apostrophe=20in=20their=20us?= =?UTF-8?q?ername=20cannot=20cancel=20their=20own=20workflow=20no=20action?= =?UTF-8?q?=20button=20shown=20=20=20=2026177:=20ALF-6686=20-=20DOCLIB=20-?= =?UTF-8?q?=20add=20'add-default-resource'=20as=20enhancement.=20=20=20=20?= =?UTF-8?q?26181:=20Fix=20for=20ALF-7179=20-=20NPE=20on=20Check=20in=20act?= =?UTF-8?q?ion=20when=20define=20lockable=20aspect=20as=20mandatory.=20=20?= =?UTF-8?q?=20=2026182:=20ALF-3145=20-=20Caller=20of=20CopyBehaviorCallbac?= =?UTF-8?q?k.getCopyProperties=20should=20ensure=20modifiability=20of=20pr?= =?UTF-8?q?operties=20map=20=20=20=20=20=20-=20Copy=20Service=20was=20alre?= =?UTF-8?q?ady=20done.=20=20=20Merged=20similar=20changes=20to=20CopyBehav?= =?UTF-8?q?iourCallback.=20=20=20=2026185:=20ALF-7238=20-=20Value=20for=20?= =?UTF-8?q?$fieldHtmlId=20changes=20after=20metadata=20refresh=20=20=20=20?= =?UTF-8?q?26196:=20Fixed=20ALF-3383:=20range=20slider=20does=20not=20hono?= =?UTF-8?q?r=20xs:fractionDigits=20=20=20=2026202:=20ALF-6947:=20RM=20LOV?= =?UTF-8?q?=20Constraint=20values=20are=20not=20returned=20in=20alphabetic?= =?UTF-8?q?=20order?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * RM list of values are now shown in the UI as ordered * this can be overridden in the constrant definition but isn't exposed in the admin UI 26206: Fixed ALF-6538 "My Tasks Dashlet needs to use Page URL" (code contribution from Jeff Potts) 26211: Fixed ALF-1289 "Search for groups on "Add Group" form works incorrectly" 26213: Fixes ALF-6697: Updates to flash and html uploaders to prevent files named with (Windows) illegal characters from being uploaded 26220: Fixed CopyServicePolicies javadoc 26221: Fixed ALF-4926: Incorrect behavior of update and move rule for the same folder - Pulled rule-specific code out of FileFolderService - Added detection for new nodes and renamed nodes in current transaction - New nodes will only fire inbound properties, renamed nodes won't fire outbound, etc - Added in coverage tests: - testCheckThatModifyNameDoesNotTriggerInboundRule - testCheckThatModifyNameDoesNotTriggerOutboundRule - testUpdateAndMoveRuleOnSameFolder - Requires regression tests of ALF-4846 and rule-based test cases - Inbound, update and outbound have to be respected for all clients 26222: Confirmed 3.4 fix presence for ALF-5001: cm:name uniqueness check can fail if the property is not set - Unit test the condition - Checked that code now uses the node UUID as a cm:name substitute 26228: Test fix after rev 25770 for ALF-6904 26232: Fixes ALF-6697: Improved error handling for HTML uploader 26236: Fixes ALF-6697: Re-use forms validation logic 26237: Build fix for lexer tests (character encoding issues on build box + using unfixed antlr test environment for one test) 26244: Merged BRANCHES/DEV/BELARUS/V3.3-2011_01_18 to BRANCHES/DEV/V3.4-BUG-FIX: (with modifications) 25071: ALF-1846: Workflow validation is not perfomed when tasks progression are requested 26259: Fix for ALF-7520: Upgrading from "old permission model" to "new permission model" (DmPermissionsPatch) - include generic patch to fix up permission inheritance issues Probable work around for ALF-7453: ACL Propagation issue for large number of users/ACLs 26276: Unit test fix for ALF-4926: Incorrect behavior of update and move rule for the same folder - Content update trigger now ignores nodes that are created in the same transaction 26277: Fix for ALF-7636, the onLoggedOut function was being called directly rather than an event being triggered, which made it impossible for plugins to use the event. 26278: Fix ALF-7568: Thumbnails should not go into the trash can - For 3.4: Just the content model setting for cm:thumbnail - Relates to TEAM rev 25038 26283: If we're not doing action tracking during execution and completion, then don't do the pending step either (ALF-7341) 26284: ALF-5998 - German language pack error in Explorer - Incorrect layout of Manage Task button in My tasks component in alfresco explorer 26285: Hopefully avoid intermittent failures caused by either slow machines (by ensuring locks are held during slow execution), and fixing up the action tracking parts of the test (broken by ALF-7341 changes) 26286: ALF-5889 - Italian translation errors in Explorer and Share - Aspects 26299: Fixed ALF-6289 "Contributor is absent in Permissions section at the details page" 26305: ALF-7264 - Improve error messages when there are no valid email addresses to send an email too (avoids a null pointer, instead gives a helpful one), and also a provisional fix for @localhost email validation (pending a proper fix via VALIDATOR-292) 26308: ALF-6073 - *.docx document is displayed on all views (Document List portlet) 26309: ALF-7532 - Content Rule on RM site (other than Folder) creates 'GUID' folder in Share Note: Fixed on Team, but along with many other unrelated fixes; hence not merged directly. 26311: Fixed ALF-7162: Bulk import NPE 26317: ALF-5560 - Incorrect behaviour on import. RM FilePlan now tolerates (but does not render) non-RM content which was causing the original issue. 26318: Fixes ALF-7321: Ensure that WCM and Share groups don't appear as options in Repository web-client start workflow wizard for group and pooled review workflows 26327: Merged V3.4 to V3.4-BUG-FIX 26158: Merged DEV/TEMPORARY to V3.4 26154: ALF-7571: Create Web Project wizard - Step 3 Superfluous “cellpadding” elements were removed. Missing space was added. 26164: ALF-6885: Changed svn:eol-style from native to LF for all .sh scripts 26165: Merged V3.4-TEAM to V3.4 26161: Fix full installer. 26178: Merged DEV/TEMPORARY to V3.4 26172: ALF-7601: 3.4.1 SDK WebServiceSamples multiple problems Configuration properties for WebServiceSamples was moved to correct place. Dependency to SDK AlfrescoEmbedded was added to classpath. It is required for FileCopyUtils from Spring which is used in WebServiceSamples. 26190: Reversed 26165 / 26161 - breaks installer building 26192: Convert TaggingServiceImplTest to use retrying transactions! 26194: ALF-7045: AVM upgrade - re-implement AVM "rename duplicates" patch as a DB upgrade script 26195: Update installer overlay files 26197: Final installer updates - built and tested on OSX 26199: Allow relocatable data for postgres - part2 26204: Attempt to avoid intermittent unit test failures in RecordsManagementAuditServiceImplTest by adding some Thread.sleep() calls to allow for asynchronous audit behaviour 26207: Set site notification to false on install (ALF-6181) 26212: Merged DEV to V3.4 26203: ALF-7605 PostgreSQL: Upgrade from 2.1.7 to 3.4.1 is failing - constraint "alf_access_control_entry_acl_id_key" does not exist 1. The statements which are drop constraints, marked as optional 2. The alter statements with new constraint names were added 26245: Added OOo port number configuration 26256: Fixes ALF-7679: Remove webscript-framework-config-custom.xml file 26270: License updates from Ashutosh 26274: Fix intermittent unit test failure with retrying transaction 26275: Possible fix to intermittent test failure. 26295: Reduce scope of retrying transaction, in a hope of fixing TaggingServiceImplTest.testOnStartupJob() 26303: And the prize for the largest number of retrying transactions in a single unit test goes to... 26307: Fixes: ALF-7704: Japanese language option not appearing in dropdown box on log in page. 26314: Another defensive sleep() in RecordsManagementAuditServiceImplTest 26325: Moved defensive sleep() in RecordsManagementAuditServiceImplTest 26329: Resolved merge issue in TaggingServiceImplTest 26332: ALF-7499: DOD5015 PublishUpdatesJob is not resilient to missing nodes 26337: Build fix - Added checks for new "sorted" parameter on ListOfValuesConstraint 26338: ALF-6004 - Verisonable aspect applied to content in Web Quick Start does not result in version information being exposed 26341: ALF-5394 Fixed issue where pooled actors (users not groups) were not working properly. 26352: Removed svn:mergeinfo 26357: ALF-5369: Disposition errors when importing FilePlan which includes custom event * missing events are now created with the information available * prevents exception when browsing imported file plan * TODO make sure sufficient information is stored in the export file so that events missing can be correctly recreated 26358: Found and fixed javascript error/bug when date-picker was used in form w read-only="true" 26360: Fixed ALF-5980 "Language pack errors in Share - Incorrect layout on Manage permissions page" 26362: Fixed ALF-5894 "Italian language pack in Share - Incorrect layout of My Profile dashlet" 26367: MERGE SWIFT to V3.4 BUG-FIX for ALF-5125 word offfice 2007 creates permanent temporary files on 3.2.2.1, and possible wrong ownership 26001 26081 26208 26216 26261 26315 26339 26370: MERGE DEV To V3.4-BUG FIX 26342 : ALF-5125 - word office 2007 creates permanent temporary files on 3.2.2.1, and possible wrong ownership 26382: Using predefined statics and neatening 26387: ALF-4101: Blog Archive filter has duplicate dates 26396: Fix for ALF-7834: CLONE -ACL Propagation issue for large number of users/ACLs - final part of fix for locking ACL changes (avoid simultaneous changes to the ACEs associated with an ACL) 26397: ALF-7823 - CIFS shuffle looses "mime type" of attachment. ALF-7670 - MS Word 2003 'Save As' to CIFS in a folder with a rule 'extract common metadata' does NOT extract the metadata 26398: Fixed ALF-6384 "Share - 'Insert Image Library' function in WIKI not working properly in IE8" 26407: Merged V3.4-2010_11_29 to V3.4-BUG-FIX 24159: ALF-413: Incorrect notification is displayed on Manage deleted items page when deleting an item that is already recovered 26412: Fix for ALF-4400: Share Search - Not Finding Document When Search Uses More Than One Tag in the Search Criteria - added TAG field - UI no longer has to do ugly query build and TAG is part of the default macro - Fixed unreported AND OR precedence issue and added grouping 26424: Fix for ALF-7795: Greater than (>) operator does not work with untokenised String properties in CMIS Query - fixed with issues will be resolved in SOLR/SWIFT - any term starting with { (used to encode locale) will be excluded from the range. - range queries not supported for urls .... 26449: Fixed ALF-5385 "Unable to edit groups on ts.alfresco.com" 26454: Fix for ALF-7852: Query consuming all heap and receiving an OOM exception - missing close on TermDocs and TermPositions 26460: ALF-634, ALF-7103 Externalized the JBPM Config location, so it can now be set as a property in repository.properties. 26470: Fixed ALF-7744: Ensure that new options.limit gets set when updating RSS feed results 26479: ALF-6533 - GROUP_EVERYONE is a special group, so when looking up the members to send an email, we need to call a different authority service method to get everyone 26484: ALF-7715 - Switch from the old Ant ZipFile to the new Commons Compress one, which supports the new file encoding zip extension 26488: ALF-7192 - Invitation code should support subtypes of Site in addition 26489: ALF-7192 - Update the Browse Bean and Site Aspect policy to support subtypes of Site 26493: ALF-7192 - When handling permissions and roles on sites, allow for sites which are a subtype of the default site type, rather than only supporting SiteModel.TYPE_SITE. (Unit test to follow) 26499: Fixes ALF-6415: Ensure that labels on installer radio buttons doesn't flow outside the window 26511: Merged DEV to V3.4-BUG-FIX 26406: ALF-7680: Check out of document allows users to create Working-Copy into Spaces where they do not have write access - Unit tests for ALF-7680 and ETHREEOH-535 were added. 26442: ALF-7680: Check out of document allows users to create Working-Copy into Spaces where they do not have write access - If destination folder for working copy is the same as the parent folder of the source node then working copy should be created even if the user has no permissions to create children in the parent of the source node. The following logic was added for Check Out operation to apply it: - if the target folder node is the same as the parent folder of the source document then working copy is created using 'System' user - if the target folder is a different parent, then working copy is created using current user. 26515: Partially fixes ALF-5774: Set correct Japanese date formatting on Repo Web Client summary panels 26529: Fix for ALF-6722 MT: Cancel workflow button isn't available for tenant users 26533: Fixed ALF-6563: Can't properly expose categories or associations on AWE forms 26534: Added correct source files for jbpm-jpdl-3.3.1 26535: Updated disabled testAsynchronousTaskExecutes as part of investigation into ALF-6405 26537: Fixed ALF-7927: Script error on Step 3 of Create Web Project Wizard - IE6/IE7 26540: ALF-7192 - Add unit test for custom site type 26559: Merged DEV to V3.4-BUG-FIX 26547: ALF-7528 : JSF - Edit online with Office 2010 causes the document mimetype to be lost - PutMethod was modified to use only guessed mime type for documents and completely ignore the Content-Type header from client. 26560: Fixes ALF-7931: Ensure checkboxes render correctly in IE6 for forms and replication job 26565: ALF-7232 - remove temporary (unit test) debug from log4j.properties 26566: Add a couple more site service checks for roles 26568: Fixes: ALF-7950 - escaped apostrophe 26576: Fix for: ALF-7996: Error when applying patch.fixAclInheritance - removed having clause for nasty where clause - hopefully optimised out .... 26580: Fixed ALF-7915 "Cannot delete rules created with check-in perform action." 26591: ALF-7995 - Repo tier web script get dataLists returns rule folder. Also fixed incorrect folder path rendering on non-Site rules page and removed reference to non-existent file. 26598: Add notes on if Transformers can be converted to Tika or not, and if not why 26601: Fixed ALF-7804: XAM: Long paths cause 'org.snia.xam.InvalidArgumentException' when XAM aspect is applied - Path-generation truncates the first characters to leave only 512 in the path - Added catch and WARN on failure to write properties to XSet (rather than fail) - Added log4j config for XAM 26603: Fixes: ALF-868 - IE Bug. 26604: Fixes: ALF-6486 - L10N bug in width of TinyMCE's dropdown lists not accommodating longer phrases in other languages. Width is now fluid 26606: Fixes: ALF-7397 - Removed repeated chars in JA dates that include long names for days of the week. 26611: Upgrade POI and Tika for ALF-7959 26612: Add test file from ALF-7959, tweaked to include the "Quick" text, and with the original user details munged 26613: Fix up unit tests after Tika upgrade for ALF-7959 26618: ALF-7959 - Convert the Outlook MSG text converter to using Tika, which fixes encoding problems 26628: Merged PATCHES/V3.1.2 to V3.4-BUG-FIX 26626: Merged DEV/TEMPORARY to PATCHES/V3.1.2 26400: ALF-607: Rules not firing on subspaces Execute RuleServiceImpl. getRules(), RuleServiceImpl. getOwningNodeRef(Rule) and RuleServiceImpl. getOwningNodeRef(Action) methods from System User. 26630: Merged HEAD to V3.4-BUG-FIX 26620: Modified to allow for multiple mime-types for Alfresco 3.3+. Related to ALF-4027. 26629: ALF-4027: Kofax Binaries corresponding to 26620 26650: Merged SWIFT to V3.4-BUG-FIX 26093: Workaround to the fact that the Solr classpath has got too large to include on a Windows command line! (32K) 26683: ALF-8045: VersionableAspect now properly resolves the namespaces of the QNames registered with excludedOnUpdateProps 26684: Resolve ALF-7515: CMIS operation getObjectRelationships() is not spec compliant 26689: Change the status code for the CIFS Trans2QueryPath response when the file does not exist. Possible fix for ALF-6727. 26691: Merged DEV/TEMPORARY to V3.4-BUG-FIX 26681: ALF-1871: FileLink and FolderLink items do not appear in WebDav Modify PropFindMethod to show file/folder links and return href of original node. Modify GetMethod to allow browser deal with file/folder links. 26692: Merged DEV/TEMPORARY to V3.4-BUG-FIX 26558: ALF-7910: It's impossible to delete folder with accentuated letter via IMAP from Outlook 2010 Call "AlfrescoImapFolder sourceNode = getFolder(user, oldMailboxName);" before decoding oldMailboxName in ImapServiceImpl.renameMailbox() method. Update JavaDoc in AlfrescoImapService. Add testRenameAccentedMailbox() test. 26693: Merged DEV/TEMPORARY to V3.4-BUG-FIX 26544: ALF-7911: Cannot contribute via IMAP if another user with only Consumer permissions has logged in first Dynamically check readOnly in AlfrescoImapFolder.isReadOnly() method. 26694: Merged DEV/TEMPORARY to V3.4-BUG-FIX (with corrections) 26343: ALF-6945 Failed Kerberos SSO auth returns HTML web page with wrong text/plain MIME type Setting content-type to text/html added for page used for failed Kerberos and NTLM authentications. 26695: Resolve ALF-7538: CMIS AtomPub: Not possible to retrieve associations defined via an Aspect. 26696: ALF-6132: Correction to handling of optional elements by Pavel 26701: Fixes ALF-8064: Ensure Windows installer respects manual service startup selection 26706: Resolve ALF-7759: MTOM is not enabled for all CMIS Web Services 26713: Resolve ALF-7994: Custom behavior is not triggered when creating content via CMIS 26717: Resolve ALF-6848: CMIS Rest: Properties Filter Parameter incorrectly functioning. 26720: Fix ALF-7977: Webform validation on change is always passing 26726: ALF-7086: Root folder has wrong Allowable Actions 26727: Resolve ALF-6266: Incorrect exception thrown when deleting a non-existing document (web-services binding) 26728: WQS: Performance enhancements. Local max throughput increased from 11 PIs/sec to 24 PIs/sec with 15 concurrent users (CPU utilization dropped from 100% to 70%) 26729: ALF-8045: Fix VersionServiceImplTest 26735: Merged DEV/TEMPORARY to V3.4-BUG-FIX 26725: ALF-3919 : WCM - JSF does not return an appropriate warning when creating content with ' ; ' character 1. The AVMNodeConverter class was modified to allow ';' character usage in web content names. 26736: Merged V3.4 to V3.4-BUG-FIX 26411: Fixes: ALF-7292 and ALF-7289, removes "· " from after Web and before Alfresco. 26413: Fixes: ALF-7765 - ensures naming consistency 26414: Fixes: ALF-7697 - Rewording the URL help text on the create site dialogue 26417: Fixes: ALF-7414, confused and hardcoded date-formatting & date formatting translations fixed. 26610: Fix for: ALF-8007: Lucene index not coherent or not up to date or we can not rely on it to check that a working copy exist. 26739: ALF-8085 - DMDeploymentTarget uses System.out 26748: ALF-7929: Script error on Web Form Details window - IE6/ IE7 - Fix up to CHK-10095 reviewed by Kev 26749: ALF-7557: Display full paths of categories in Explorer (reviewed by Kev) 26751: ALF-8079: NPE in ContentDiskDriver 26754: Fixes: ALF-2984. parseInt octal bug, so forcing dec. 26761: Merged V3.4 to V3.4-BUG-FIX (RECORD ONLY) 26760: Merged V3.4-BUG-FIX to V3.4 26759: Merged V3.4-BUG-FIX to V3.4 (3.4.2) 26762: ALF-8028 ResultSet not closed in TransferServiceImpl2 try...finally pattern was added to search operation. 26764: Further fixes to teh patch for: ALF-7834: CLONE -ACL Propagation issue for large number of users/ACLs 26765: ALF-634, ALF-7103: Possible fix to JBPM regressions introduced by r26460 26788: Further fixes to the patch for: ALF-7834: CLONE -ACL Propagation issue for large number of users/ACLs - fix type that would have missed a minor error (for unused shared ACLs) 26815: ALF-5500: Support site subtypes in SPP 26863: Merged DEV/TEMPORARY to V3.4-BUG-FIX 26853: ALF-3792: Copy Access Should Be More Restrictive Checking permissions for “Copy” was added action. Security settings for FileFolderService.copy operations were made more strict for Alfresco and RM. 26890: Merged HEAD to V3.4-BUG-FIX 26856: Fixed ClientInfo is null in sessionLoggedOn event. JLAN-121. 26867: Fix for wildcard search handling returning dot and dot-dot file entries. ALF-4960. 26868: Fix for wildcard search handling returning dot and dot-dot file entries, repo filesystem. ALF-4960. 26888: CIFS path broken when ß (German sz) char is in folder name. ALF-7186. 26909: AVMRepository: add missing error info - report store name (if not found) 26934: Fixed ALF-6532: Upload new version fails in Share (checkout) when using ContentStoreSelector - Includes investigative tests from DEV rev 26902 - Fixed safeCopyContent to cater for first-time setting of property where content is already in new store (copy operations) - Tested XAM use-case as well 26996: Fix for ALF-8229. patch.webSiteAddModerated upgrade error. Trivial fix sanctioned for check-in on 3.4.2 by SteveR. 26997: Undoing accidental check-in of eclipse project files changes. 27045: Change to CIFS session setup exception processing as per ALF-229. 27069: ALF-3871: Global properties now visible through JMX 27108: Merged PATCHES/V3.3.3 to V3.4-BUG-FIX 27107: ALF-8388: Merged PATCHES/V3.3.4 to PATCHES/V3.3.3 26894: ALF-7237: Further diagnostics for maxChecks and maxCheckTime 27110: Merge DEV to V3.4-BUG-FIX 26859 : ALF-6546 - JCR export fails when node has a null property value 27116: ALF-8190 - Minor version labels non-intuitive now the minor version will start at 0.1 rather than 1.0 27121: Fixed ALF-8307: Audit query template in 3.4.0 should quote key values for application and username 27332: Follow on test corrections from the fix to ALF-8190. 27508: Merge Dev to V3.4-BUG-FIX 27153 : ALF-5496 Commenting on a Space creates an Empty Folder via CIFS 27575: Merged DEV/TEMPORARY to V3.4-BUG-FIX 27070: ALF-4954: when cookies are disabled, Share enters into a loop, Explorer outputs a java.lang.NullPointerException exception, Firefox 1. SSOAuthenticationFilter and login page for Share application was modified to handle lack of cookies in a manner web-client do that. 27586: Ignore default build directory for virtual server 27587: Fix for ALF-8188: Share - Search returns no result when using special characters - not really a bug .... added implementation to support \u0000 style encoding in the parser but not the impl 27590: Merged DEV to V3.4-BUG-FIX 27147: ALF-7979 : Metadata extracters should log a warning/error when type conversion of field values fails 1. Adding additional logging for type conversion failure during metadata extraction. - Additionally removed some false TODOs and fixed generics in class 27593: Fix for: ALF-7827: CMIS Contains does not support TEXT, ALL, d:content etc as would be expected - added to match Alfresco FTS 27595: Fix for: ALF-8073: Property value not stored in Lucene index if stored=true and tokenized=false - if stored is selected the value will be stored in the plain property entry 27599: Merged V3.3 to V3.4-BUG-FIX 27130: Merged DEV/TEMPORARY to V3.3 27129: ALF-8141: Using the copy/move action causes the Path QName to change to 'copy' or 'move' MoveActionExecuter: Remove PARAM_ASSOC_TYPE_QNAME and PARAM_ASSOC_QNAME(Also remove it from classes that use them). Use FileFolderService for move operation. CopyActionExecuter: Remove PARAM_ASSOC_TYPE_QNAME and PARAM_ASSOC_QNAME(Also remove it from classes that use them). Use CopyService with original assocTypeQName and assocQName(Don't use FileFolderService here because it doesn't respect deep copy flag). 27155: ALF-8141: Fix up unit tests plus RESTful calls to RuleService 27601: Fix up unit tests following merge of ALF-8141 27604: Fix for ALF-7738: Hyphen not handled correctly in cmis-alfresco search for Aspects/types : " no viable alternative at character 'a' " - query names are now escaped where required 27613: Upgrade Tika and POI for ALF-7978 27618: ALF-1813 Fixed security issue where tasks could be edited by a user who did not have acess tot he task. 27637: Merged DEV/TEMPORARY to V3.4-BUG-FIX 26965: ALF-8258: sharedLockTokens property duplicated many times Changed LockInfo.sharedLockTokens from LinkedList to HashSet because in RFC "Lock token URIs MUST be unique across all resources for all time.", and therefore sharedLockTokens should contain non-repeatable values. 27662: ALF-8549: activities.getFeedControls() does not return a scriptable object in Javascript - also fix REST call (/api/activities/feed/controls) to return JSON 27663: Perf improvement: cache User Feed Controls (for generator job run) - note: activity feed controls are not currently exposed via Share UI 27668: ALF-8413: Share Feed Generator on Oracle generates NPE (when commenting in repo view) - fix NPE (note: in general Share does not post activities outside of site context + feed generator is currently based on site members) 27669: ALF-8549: activities.getFeedControls() does not return a scriptable object in Javascript - quick test fix for PostgreSQL (follow-on from r27662) 27697: ALF-8581: User activities are displayed twice in My Activities dashlet (in cluster env) - add cluster job lock service (SLNG-770) 27776: ALF-8581: Reverted changes to FeedGeneratorJob 27806: Merged DEV to V3.4-BUG-FIX 27793: ALF-8351 : JBPM Tables in Oracle missing index on Foreign Keys leading to table Locks 1. Sql script that creates indexes for foreign keys in jbpm tables was implemented. This script synchronizes jbpm indexes with mysql version. Merge changes: Changed 4.0 references to 3.4; Incremented schema number. 27808: Fixed ALF-7510: Share - Workflow 'due date' field date validation doesn't work properly 27811: Fixed ALF-6179: WebDAV has problems if username contains spaces 27812: Partial fix for ALF-7032: Alfresco doesn't escape special XML characters in AtomPub 27815: Switched to use ?xml instead of ?html in partial fix for ALF-7032: Alfresco doesn't escape special XML characters in AtomPub 27825: ALF-8489 Fixed issue by removing inappropriate 'requiredApprovePercent' field. 27852: Fix for ALF-7845 index.recovery.mode=AUTO doesn't rebuild avm index - applied provided patch 27857: Fix for ALF-868: Large Table Data causes TinyMCE to drop below Alfresco Footer in Web Form - IE7 only 27860: Fixes: ALF-2199 Errors in CSS files. 27866: Fixes: ALF-1327 - truncation issues in the calendar. 27881: Fixes bug in Chrome introduced with r27866 27898: CIFS on Windows fails to start under certain conditions. ALF-8723 27901: Merged DEV to V3.4-BUG-FIX 27891: ALF-7421: An opensolaris NFS client cannot see folders renamed using the web UI after two minutes. NFS server cache updating policy handler was implemented: - NfsServerNodeMonitor.java’ – new policy handler for NFS server; - NFSServerBean.java’ was modified to configure implemented node monitor with NFS server instance during server start up; - file-servers.properties’ was expanded with new properties for new node monitor which allows controlling node monitor 'enabled' state; - file-servers-context.xml’ was expanded with bean configuration for new node monitor and with configuration for NFS server bean to accept newly configured node monitor bean; - ShareDetailsHash.java’ was modified to externalize hashtable of the cache; - NFSServer.java’ was modified to externalize its cache for public access 27902: ALF-8744 - Untransalted strings in Recent shapshot 27903: Fixes: ALF-5717 Issues with non i18n/L10N Data List form fields. 27922: Fixes: ALF-8726 by back porting some Team usability fixes. *Do Not Merge* 27923: Fixes: ALF-8429 - Replaces hard coded English tooltip with a pre-existing i18n string. 27929: Fixed ALF-8768: Wrong path in comment for wcm-bootstrap-context.xml 27938: Partial Fix for: ALF-8720: Adds missing spaces. 27940: Merged V3.3 to V3.4-BUG-FIX 27851: Fix for ALF-8476:CLONE -Query consuming all heap and receiving an OOM exception - actually fixes stack overflow with skipTo when there are lots of deleted docs in an index in a row (<10000 on the default settings) ---- Modified : /alfresco/BRANCHES/DEV/V3.4-BUG-FIX Modified : /alfresco/BRANCHES/DEV/V3.4-BUG-FIX/root/projects/repository/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java Modified : /alfresco/BRANCHES/DEV/V3.4-BUG-FIX/root/projects/repository/source/java/org/alfresco/repo/search/impl/lucene/FilterIndexReaderByStringId.java 27942: Merged V3.4 to V3.4-BUG-FIX 26772: Fix for ALF-7843 - Created via SPP all-day event displays incorrectly 26775: Fix for ALF-3374 - Reverting a file in workflow causes a Successful error message 26778: Fix message Failed to find I18N message key: reset_categories for locale: en_US 26781: Fix for ALF-6488 - LangPack FR - [Site's Wiki] Renaming a wiki's page - special chars handling 26783: Fixed ALF-7421: An opensolaris NFS client cannot see folders renamed using the web UI after two minutes. - De-Hibernate DAO refactor missed implementing 'system.enableTimestampPropagation' - Set 'system.enableTimestampPropagation=true' in alfresco-global.properties - Timestamps will be written to immediate parent folder and will therefore not drop out of the filestate cache after 2 min 26785: Services-layer fix for ALF-8036. Incorrect permissions copied when copying folder with permissions from one site to another. UI will have to make changes in the UI layer in order to use new Services methods, thus completing the fix. This check-in adds support in the SiteService for moving and copying site-contained nodes. New move/copy methods are available in the Java Foundation API and in the JavaScript API which mimic the signatures of those in the NodeService and CopyService respectively. These methods simply delegate to the back-end services and so behaviours should be the same as before if they are used in place of the node and copy service methods. With one exception: The SiteService wrapper methods detect when the relocated node has been copied/moved between two different Share sites and clears permissions from the relocated node (and its primary descendants) that refer to the previous site. This has not been implemented using policies/behaviours as we can't bind the behaviour to any particular content class. Nodes of any type could be copied/moved between sites. 26787: Removed erroneous import from StreamContent - incorrect dependency on de.schlichtherle.io.FileOutputStream 26803: Fixes: ALF-8138, adds a i18n property for a ToDo list's attachment title. 26806: Fixed ALF-8154: Shorten the node path as prefix-only path - Use Path.toPrefixString instead of Path.toString - Provides improved fix for ALF-7804 26824: Fixed ALF-7012: WCM - Schema error "xs:enumeration full" select button group shows "Please select..." 26832: UI-layer fix component for ALF-8036 and a refactoring of the Services-layer fix component. Rather than add various facade methods to the SiteService for all the copy & move variants in NodeService, CopyService, FileFolderService etc, I have added a single new method to the SiteService cleanSitePermissions(). This removes all out-of-date site permissions after a node has been moved or copied to a new site. Also changed the slingshot action webscripts for move-to and copy-to to call this cleanUp method. 26838: Rewording a misleading code comment. Related to ALF-8036 changes. 26847: ALF-6727: File server protocols don't report the read only attribute for folders unless new configuration setting filesystem.setReadOnlyFlagOnFolders is true - Default is false - On windows, the read only attribute doesn't mean the folder is read only - it means "this folder has been customized - please fetch a desktop.ini". - This results in poor performance for non-admin users due to lots of secondary fetches for configuration information. - See the bug for more details. 26855: ALF-6727: Missing files from last check in 26858: Merged V3.4-TEAM to V3.4 26841: Fix for ALF-1044. (Searching for groups whose names contain regex reserved chars.) The fix was to add [] as reserved chars to the RegEx SimpleLanguageDef. Thanks AndyH. 26860: ALF-7101 and ALF-7866 - don't show the rules options to collaborators, as they shouldn't be able to create/manage rules 26861: Merged DEV/TEMPORARY to V3.4 (with corrections) 26157: ALF-1544: Server won't start on JBoss: Unable to resolve drag and drop application as a file, class path resource [alfresco/desktop/Alfresco.exe] DesktopAction uses Resource’s InputStream for drug-n-drop files representation. 26870: ALF-6727: Fix up ContentDiskDriverTest 26898: Fix ALF-8219: Deploying WQS in the same container as Alfresco causes startup to stall 26899: Possible installer build fix - uncomment project.readmeFile - set failifexecutionfails="true" on installer build targets so we know if installer building failed! 26900: ALF-8180: The installer shouldn't randomly delete a directory called tomcat that it didn't create - Fix provided by Bitrock 26919: Backing out 3 changes related to ALF-8036 due to uncertainty over fix approach. I'm reverse-merging revisions 26785, 26832 and 26838 out of this 3.4.2 branch. The fix version for ALF-8036 has been changed to 3.4.3 and so we don't want an unfinished fix on 3.4.2. In fact, this fix may be finished, but there is some uncertainty now over the approach to adopt (strip permissions vs. prevert copying of permissions). This fix will go to Team and 3.4.3 instead. 26932: Fixed a couple of non-unicode French characters that were messing up my scripts and: Merged BRANCHES/DEV/dwebster/ to BRANCHES/V3.4: 26911: Latest updates from translators (based on rev26710) 26944: ALF-3569 - Alfresco repository CIFS driver not setting timestamps. 26952: Finishes the completeness tests & fixes several L10N bugs. 26972: Fixes ALF-8272: Alfresco cannot be started after fresh install 26980: ALF-8287 - All buttons are disabled on Data Lists page. 27039: Merged BRANCHES/DEV/dwebsterV34 to BRANCHES/V3.4: - Update from Translators (based on r26837). 27059: Merged PATCHES/V3.4.1 to V3.4 26959: ALF-8261: 3.4 JSF performance regression introduced by WebProjectServiceImpl.hasWebProjectsRoot() (ALF-3085) 27020: ALF-8281: CLONE -'Move To' operation for categories, folders and records doesn't work ESCALATION 27061: Merged PATCHES/V3.3.4 to V3.4 27032: ALF-8289: Merged DEV to PATCHES/V3.3.4 27028: ALF-8289: Occasionally, web form complains of missing required fields when the fields are indeed populated 27068: Merged DEV to V3.4 27067: ALF-3774 : Unable to disable listening on port 7500 (JGroups) 1. DummyProtocol was changed to rewrite default configuration. This changes turn off diagnostic probing for DummyChannel. 27071: Fixed ALF-8363: BadSqlGrammarException during patch.fixAclInheritance on Oracle - Using '... = true' does not work for Oracle. - Replaced with parameterClass="boolean" and #trueOrFalse# - Tested against limited dataset so some condition paths might be missed. 27083: ALF-8124: Corrected ${} placeholders in Japanese installer strings 27088: Fixed ALF-8377: Generic KeywordSearch is not resilient to stale Lucene indexes - Cleaned up PersonSearchTest - Added exists check to KeywordSearch - Rationalized related bean declarations 27113: Fixed ALF-1322 "Creating user with username containing spaces works incorrectly" 27127: ALF-8346 - 'Complete event' button for folders is disabled in IE 27136: Installer string updates from Gloria 27173: Fix for: ALF-7834: CLONE - ACL Propagation issue for large number of users/ACLs - manually fix up the cache when changing inheritance 27503: Fix for ALF-8345 - Extra icons on the form of adding translation without content(IE specific) 27504: Fix for ALF-6917 - A system error happens when user attempted press toggle 'Version History' 27505: ALF-6215 - French Language pack - It's incorrect layout in Document List component 27509: ALF-8451: Port AVM-rename-dupes.sql to DB2 and SQL Server 27511: ALF-7165: User with contributor role cannot view content 27512: Installer string updates from Gloria - Corrections to msgid Installer.ReadmeFile.View 27557: case sensitivity issues with terminfo database building from Windows 27563: Merged DEV to V3.4 27558: ALF-8408: Impossible to upload a new file by contributor to subspace when rule is applied to subspaces Retrieving of rule was surrounded in RunAs(System username) block. It allows to retrieve rule node and its actions if user has no read permissions to rule node and to rule's actions. JUnit test "PermissionsForPropagatedRules_ALF_8408" was added. 27602: Fix for ALF-5625 - When viewing properties in version history hitting close results in loop (V3.4.2) 27622: ALF-5607: Remove bundled postgres data directory on installation abort on Linux 27639: Fix for ALF-6488 - Decode HTML content before creating page url argument for wiki 27647: Fix for ALF-8552 27672: ALF-8553: PatchService didn't recurse on dependents of already applied patches - Hence patch ordering was wrong on V3.4 upgrade 27684: ALF-8553, ALF-8602: set batchMaxQueryRange on patch.fixNameCrcValues-2 to avoid OOM 27687: Merge DEV to V3.4 27674 : ALF-8453 CIFS Failed to save versionable MS Word content as collaborator 27690: Merged DEV/TEMPORARY to V3.4 27688: ALF-7822 : AVM projects unavailable after upgrade to 3.4.1 script was modified to prevent data corruption 27775: Fixes ALF-8654: Add removed quotes back into .po files 27805: ALF-2935: Properly control RMI port in Bitrock installer - order repository-properties before shared-properties so that you can specify the port in alfresco-global.properties if you want to - include a tokenized alfresco-shared.properties in the WCM installation files 27807: Merged V3.4-TEAM to V3.4 27756: ALF-8207 - ALL LANG - Themes are not translated 27840: ALF-8687: No items display for series when opening it from library list 27853: Fixes: ALF-7465 - Removes some of the differences between the Add Event and Remove Event dialogues & reuses existing TagLibrary component to avoid conflicts. 27856: ALF-8687: No items display for series when opening it from library list. Slight refactor to better match non-RM doclib code. 27871: Fixes ALF-8124: Ensure Spanish locale for installer shows product name correctly 27932: Fixes ALF-5519: alfresco-enterprise-wcmqs-3.3.3.zip does not contain awe.war - Added awe.war to WQS zip file 27944: Merged V3.4 to V3.4-BUG-FIX (RECORD ONLY) 26766: Merged V3.4-BUG-FIX to V3.4 26761: Merged V3.4 to V3.4-BUG-FIX (RECORD ONLY) 26760: Merged V3.4-BUG-FIX to V3.4 26759: Merged V3.4-BUG-FIX to V3.4 (3.4.2) 26762: ALF-8028 ResultSet not closed in TransferServiceImpl2 try...finally pattern was added to search operation. 26764: Further fixes to the patch for: ALF-7834: CLONE -ACL Propagation issue for large number of users/ACLs 26765: ALF-634, ALF-7103: Possible fix to JBPM regressions introduced by r26460 26831: Merged V3.4-BUG-FIX to V3.4 26788: Further fixes to the patch for: ALF-7834: CLONE -ACL Propagation issue for large number of users/ACLs - fix type that would have missed a minor error (for unused shared ACLs) 27016: Merged V3.4-BUG-FIX to V3.4 26996: Fix for ALF-8229. patch.webSiteAddModerated upgrade error Trivial fix sanctioned for check-in on 3.4.2 by SteveR 26997: Undoing accidental check-in of eclipse project files changes 27859: Merged BRANCHES/DEV/V3.4-BUG-FIX to BRANCHES/V3.4: 27857: Fix for ALF-868: Large Table Data causes TinyMCE to drop below Alfresco Footer in Web Form - IE7 only git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@27948 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/action-services-context.xml | 4 +- config/alfresco/application-context-core.xml | 1 - config/alfresco/bootstrap-context.xml | 18 + .../bootstrap/webscripts/folder.get.atom.ftl | 10 +- config/alfresco/core-services-context.xml | 21 +- config/alfresco/dao/dao-context.xml | 2 + .../AlfrescoPostCreate-JBPM-FK-indexes.sql | 109 + .../AlfrescoPostCreate-JBPM-FK-indexes.sql | 22 + .../AVM-rename-dupes.sql | 10 +- ...rchar-field-sizes-quadruple-increasing.sql | 21 + config/alfresco/form-services-context.xml | 10 +- config/alfresco/hibernate-context.xml | 2 + .../patch-common-SqlMap.xml | 147 + .../alfresco/invitation-service-context.xml | 3 +- config/alfresco/jbpm-context.xml | 2 +- .../alfresco/jgroups/alfresco-jgroups-TCP.xml | 1 + .../messages/action-config_ja.properties | 18 +- .../messages/bootstrap-spaces_de.properties | 6 +- .../messages/bootstrap-spaces_es.properties | 5 - .../messages/bootstrap-spaces_fr.properties | 5 - .../messages/bootstrap-spaces_ja.properties | 191 +- ...otstrap-webScriptsExtensions_ja.properties | 8 +- .../bootstrap-webScripts_ja.properties | 6 +- .../messages/content-model_it.properties | 58 +- .../messages/data-list-model.properties | 1 + .../messages/data-list-model_de.properties | 1 + .../messages/data-list-model_es.properties | 2 + .../messages/data-list-model_fr.properties | 1 + .../messages/data-list-model_it.properties | 1 + .../messages/data-list-model_ja.properties | 3 +- .../messages/invitation-service_fr.properties | 2 +- .../messages/invitation-service_it.properties | 7 +- .../messages/invitation-service_ja.properties | 35 +- .../messages/patch-service.properties | 7 +- .../messages/patch-service_de.properties | 15 +- .../messages/patch-service_es.properties | 15 +- .../messages/patch-service_fr.properties | 15 +- .../messages/patch-service_it.properties | 16 +- .../messages/patch-service_ja.properties | 15 +- .../messages/site-service_de.properties | 2 +- .../messages/site-service_fr.properties | 2 +- .../messages/site-service_it.properties | 2 +- .../messages/site-service_ja.properties | 20 +- .../messages/transfer-model_fr.properties | 170 +- .../messages/transfer-service_de.properties | 2 +- .../messages/transfer-service_fr.properties | 2 +- .../messages/transfer-service_it.properties | 2 +- .../messages/transfer-service_ja.properties | 88 +- .../alfresco/messages/wdr-messages.properties | 12 + config/alfresco/model/contentModel.xml | 1 + .../alfresco/patch/patch-services-context.xml | 46 +- .../public-services-security-context.xml | 2 +- config/alfresco/repository.properties | 3 + config/alfresco/site-services-context.xml | 1 + .../default/activities-feed-context.xml | 1 + .../default/file-servers-context.xml | 17 +- .../default/file-servers.properties | 8 +- .../default}/network-protocol-context.xml | 8 + config/alfresco/version.properties | 2 +- config/alfresco/wcm-services-context.xml | 1 + ...-moderated-workflow-messages_ja.properties | 2 +- .../wcm-workflow-messages_ja.properties | 4 +- config/test/alfresco/model/testWcmModel.xml | 24 + .../wcm-template-node-test-context.xml | 21 + .../cmis/CMISRelationshipDirectionEnum.java | 2 +- .../java/org/alfresco/cmis/CMISServices.java | 2 +- .../CMISBasePropertyDefinition.java | 3 +- .../CMISDocumentTypeDefinition.java | 3 +- .../dictionary/CMISFolderTypeDefinition.java | 3 +- .../dictionary/CMISObjectTypeDefinition.java | 3 +- .../dictionary/CMISPolicyTypeDefinition.java | 3 +- .../CMISRelationshipTypeDefinition.java | 3 +- .../CMISStrictDictionaryService.java | 17 +- .../alfresco/cmis/mapping/CMISMapping.java | 23 +- .../cmis/mapping/CMISServicesImpl.java | 24 +- .../cmis/mapping/RootActionEvaluator.java | 71 + .../cmis/search/CMIS-query-test-model.xml | 24 + .../search/CmisFunctionEvaluationContext.java | 61 +- .../org/alfresco/cmis/search/QueryTest.java | 55 + .../org/alfresco/filesys/NFSServerBean.java | 19 +- .../filesys/NfsServerNodeMonitor.java | 391 +++ .../filesys/ServerConfigurationBean.java | 11 - .../filesys/alfresco/AlfrescoDiskDriver.java | 3 + .../filesys/alfresco/DesktopAction.java | 26 +- .../alfresco/DesktopActionException.java | 4 +- .../alfresco/MultiTenantShareMapper.java | 4 +- .../cifs/EnterpriseCifsAuthenticator.java | 11 +- .../filesys/config/FTPConfigBean.java | 24 - .../config/ServerConfigurationBean.java | 4 - .../org/alfresco/filesys/repo/CifsHelper.java | 53 +- .../filesys/repo/ContentDiskDriver.java | 900 +++--- .../filesys/repo/ContentDiskDriverTest.java | 2570 +++++++++++++++++ .../filesys/repo/ContentQuotaManager.java | 4 +- .../filesys/repo/ContentSearchContext.java | 6 +- .../repo/desk/JavaScriptDesktopAction.java | 103 +- .../alfresco/filesys/repo/package-info.java | 19 + .../jcr/exporter/JCRDocumentXMLExporter.java | 7 +- .../jcr/exporter/JCRSystemXMLExporter.java | 13 +- .../java/org/alfresco/jcr/item/ItemTest.java | 6 +- .../repo/action/ActionServiceImpl.java | 8 +- .../repo/action/ActionServiceImplTest.java | 3 +- .../action/ActionTrackingServiceImplTest.java | 7 +- .../executer/ContentMetadataExtracter.java | 8 + .../action/executer/CopyActionExecuter.java | 25 +- .../executer/ImporterActionExecuter.java | 34 +- .../action/executer/MailActionExecuter.java | 36 +- .../action/executer/MoveActionExecuter.java | 51 +- .../feed/AbstractFeedGenerator.java | 123 +- .../activities/feed/FeedTaskProcessor.java | 55 +- .../repo/activities/script/Activity.java | 13 +- .../activities/script/test_activityService.js | 13 +- .../repo/admin/patch/PatchServiceImpl.java | 38 +- .../patch/impl/FixAclInheritancePatch.java | 394 +++ .../impl/SitePermissionRefactorPatch.java | 7 +- .../alfresco/repo/avm/AVMNodeConverter.java | 14 +- .../org/alfresco/repo/avm/AVMRepository.java | 134 +- .../repo/avm/AVMServicePermissionsTest.java | 92 + .../org/alfresco/repo/avm/AVMStressTestP.java | 4 +- .../repo/coci/CheckOutCheckInServiceImpl.java | 45 +- .../coci/CheckOutCheckInServiceImplTest.java | 145 + .../repo/content/AbstractContentAccessor.java | 4 +- .../AbstractMappingMetadataExtracter.java | 19 +- .../TikaAutoMetadataExtracterTest.java | 28 +- .../content/transform/EMLTransformer.java | 11 + .../HtmlParserContentTransformer.java | 4 + .../transform/MailContentTransformer.java | 178 +- .../transform/MailContentTransformerTest.java | 27 + .../MediaWikiContentTransformer.java | 3 + .../copy/CompoundCopyBehaviourCallback.java | 13 +- .../repo/copy/CopyServiceImplTest.java | 2 - .../repo/copy/CopyServicePolicies.java | 3 +- .../dictionary/RepoDictionaryDAOTest.java | 2 +- .../repo/domain/node/AbstractNodeDAOImpl.java | 190 +- .../domain/patch/AbstractPatchDAOImpl.java | 1 + .../alfresco/repo/domain/patch/PatchDAO.java | 32 + .../domain/patch/ibatis/PatchDAOImpl.java | 58 +- .../permissions/AVMAccessControlListDAO.java | 10 + .../permissions/AccessControlListDAO.java | 2 + .../repo/domain/permissions/AclDAO.java | 6 + .../repo/domain/permissions/AclDAOImpl.java | 463 +-- .../AbstractWorkflowFormProcessor.java | 11 + .../processor/workflow/TaskFormPersister.java | 5 +- .../processor/workflow/TaskFormProcessor.java | 2 +- .../workflow/TaskFormProcessorTest.java | 2 + .../workflow/WorkflowFormPersister.java | 5 +- .../workflow/WorkflowFormProcessor.java | 2 +- .../workflow/WorkflowFormProcessorTest.java | 6 +- .../repo/imap/AlfrescoImapFolder.java | 25 +- .../org/alfresco/repo/imap/ImapService.java | 18 +- .../alfresco/repo/imap/ImapServiceImpl.java | 10 +- .../repo/imap/ImapServiceImplTest.java | 16 + .../importer/ACPImportPackageHandler.java | 10 +- .../invitation/InvitationServiceImpl.java | 12 +- .../AlfrescoJGroupsChannelFactory.java | 6 + .../org/alfresco/repo/jscript/ScriptNode.java | 34 +- .../filefolder/FileFolderServiceImpl.java | 12 - .../filefolder/FileFolderServiceImplTest.java | 153 +- .../ml/tools/EditionServiceImplTest.java | 14 +- .../repo/node/BaseNodeServiceTest.java | 35 + .../alfresco/repo/node/index/NodeIndexer.java | 12 +- .../repo/ownable/impl/OwnableServiceImpl.java | 82 +- .../repo/rule/RuleServiceCoverageTest.java | 124 +- .../alfresco/repo/rule/RuleServiceImpl.java | 124 +- .../repo/rule/RuleServiceImplTest.java | 69 + ...foreDeleteChildAssociationRuleTrigger.java | 15 +- .../ruletrigger/CreateNodeRuleTrigger.java | 10 +- .../OnContentUpdateRuleTrigger.java | 14 + .../OnCreateChildAssociationRuleTrigger.java | 30 +- .../OnPropertyUpdateRuleTrigger.java | 36 +- .../repo/rule/ruletrigger/RuleTrigger.java | 6 +- .../rule/ruletrigger/RuleTriggerTest.java | 60 +- ...hotTriggeredIndexingMethodInterceptor.java | 2 + .../impl/lucene/ADMLuceneIndexerImpl.java | 7 +- .../search/impl/lucene/ADMLuceneTest.java | 324 ++- .../lucene/FilterIndexReaderByStringId.java | 18 +- .../impl/lucene/index/IndexInfoTest.java | 27 + ...nceCountingReadOnlyIndexReaderFactory.java | 9 +- .../repo/search/impl/parsers/CMISTest.java | 1 - .../search/impl/parsers/CMIS_FTSTest.java | 1 - .../ACLEntryAfterInvocationProvider.java | 10 +- .../org/alfresco/repo/site/SiteAspect.java | 16 +- .../alfresco/repo/site/SiteServiceImpl.java | 69 +- .../repo/site/SiteServiceImplTest.java | 157 +- .../repo/site/script/ScriptSiteService.java | 15 +- .../org/alfresco/repo/site/script/Site.java | 4 +- .../repo/tagging/TaggingServiceImplTest.java | 38 +- .../UpdateTagScopesActionExecuter.java | 9 +- .../repo/template/AVMTemplateNode.java | 9 +- .../repo/template/AVMTemplateNodeTest.java | 101 + .../repo/transfer/TransferServiceImpl2.java | 57 +- .../transfer/TransferServiceImplTest.java | 46 + .../alfresco/repo/usage/ContentUsageImpl.java | 12 +- .../alfresco/repo/usage/UsageServiceImpl.java | 2 +- .../usage/UserUsageTrackingComponent.java | 15 +- .../repo/version/NodeServiceImplTest.java | 14 +- .../repo/version/VersionServiceImplTest.java | 18 +- .../repo/version/VersionableAspect.java | 106 +- .../SerialVersionLabelPolicy.java | 34 +- .../SerialVersionLabelPolicyTest.java | 6 +- ...bstractWorkflowServiceIntegrationTest.java | 76 +- .../repo/workflow/PackageManager.java | 33 +- .../alfresco/repo/workflow/TaskUpdater.java | 6 +- .../repo/workflow/WorkflowBuilder.java | 8 +- .../repo/workflow/WorkflowServiceImpl.java | 48 +- .../workflow/jbpm/AlfrescoExecuteNodeJob.java | 81 + .../repo/workflow/jbpm/AlfrescoTaskNode.java | 62 + .../repo/workflow/jbpm/AlfrescoTimer.java | 38 +- .../JbpmWorkflowServiceIntegrationTest.java | 78 + .../workflow/jbpm/jbpm.ExecuteNodeJob.hbm.xml | 15 + .../repo/workflow/jbpm/jbpm.TaskNode.hbm.xml | 15 + .../service/cmr/action/ExecutionSummary.java | 4 + .../service/cmr/site/SiteService.java | 15 +- .../service/cmr/usage/UsageService.java | 3 +- .../org/alfresco/util/schemadump/Main.java | 3 +- .../alfresco/wcm/asset/AssetServiceImpl.java | 5 +- .../wcm/webproject/WebProjectServiceImpl.java | 76 +- .../filesys/ContentDiskDriverTest1.docx | Bin 0 -> 11302 bytes .../filesys/ContentDiskDriverTest2.docx | Bin 0 -> 11265 bytes .../async_adhoc_processdefinition.xml | 51 + source/test-resources/quick/quick.chinese.msg | Bin 0 -> 48129 bytes 220 files changed, 8629 insertions(+), 2114 deletions(-) create mode 100644 config/alfresco/dbscripts/create/org.hibernate.dialect.Dialect/AlfrescoPostCreate-JBPM-FK-indexes.sql create mode 100644 config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoPostCreate-JBPM-FK-indexes.sql create mode 100644 config/alfresco/dbscripts/upgrade/3.4/org.hibernate.dialect.Dialect/varchar-field-sizes-quadruple-increasing.sql create mode 100644 config/alfresco/messages/wdr-messages.properties rename config/alfresco/{ => subsystems/fileServers/default}/network-protocol-context.xml (90%) create mode 100644 config/test/alfresco/model/testWcmModel.xml create mode 100644 config/test/alfresco/wcm-template-node-test-context.xml create mode 100644 source/java/org/alfresco/cmis/mapping/RootActionEvaluator.java create mode 100644 source/java/org/alfresco/filesys/NfsServerNodeMonitor.java create mode 100644 source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java create mode 100644 source/java/org/alfresco/filesys/repo/package-info.java create mode 100644 source/java/org/alfresco/repo/admin/patch/impl/FixAclInheritancePatch.java create mode 100644 source/java/org/alfresco/repo/template/AVMTemplateNodeTest.java create mode 100644 source/java/org/alfresco/repo/workflow/jbpm/AlfrescoExecuteNodeJob.java create mode 100644 source/java/org/alfresco/repo/workflow/jbpm/AlfrescoTaskNode.java create mode 100644 source/java/org/alfresco/repo/workflow/jbpm/jbpm.ExecuteNodeJob.hbm.xml create mode 100644 source/java/org/alfresco/repo/workflow/jbpm/jbpm.TaskNode.hbm.xml create mode 100644 source/test-resources/filesys/ContentDiskDriverTest1.docx create mode 100644 source/test-resources/filesys/ContentDiskDriverTest2.docx create mode 100644 source/test-resources/jbpmresources/async_adhoc_processdefinition.xml create mode 100644 source/test-resources/quick/quick.chinese.msg diff --git a/config/alfresco/action-services-context.xml b/config/alfresco/action-services-context.xml index bf78e2427a..05a962a69f 100644 --- a/config/alfresco/action-services-context.xml +++ b/config/alfresco/action-services-context.xml @@ -442,8 +442,8 @@ - - + + false diff --git a/config/alfresco/application-context-core.xml b/config/alfresco/application-context-core.xml index 30665d99cb..ec66e257a1 100644 --- a/config/alfresco/application-context-core.xml +++ b/config/alfresco/application-context-core.xml @@ -15,7 +15,6 @@ - diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index b6c6afc6ab..75f88ee3cf 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -90,6 +90,7 @@ classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoPostCreate-JBPM-Extra.sql + classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoPostCreate-JBPM-FK-indexes.sql @@ -136,6 +137,8 @@ + + @@ -524,6 +527,21 @@ + + + + + + contentDiskDriver + + + + + org.alfresco.jlan.server.filesys.DiskInterface + + + + diff --git a/config/alfresco/bootstrap/webscripts/folder.get.atom.ftl b/config/alfresco/bootstrap/webscripts/folder.get.atom.ftl index 098fe81dfa..d1df69f7d2 100644 --- a/config/alfresco/bootstrap/webscripts/folder.get.atom.ftl +++ b/config/alfresco/bootstrap/webscripts/folder.get.atom.ftl @@ -1,12 +1,12 @@ Alfresco (${server.edition}) - Folder: ${folder.displayPath}/${folder.name} + Folder: ${folder.displayPath}/${folder.name?xml} ${xmldate(date)} ${absurl(url.context)}/images/logo/AlfrescoLogo16.ico <#list folder.children as child> - ${child.name} + ${child.name?xml} <#if child.isContainer> <#else> @@ -15,10 +15,10 @@ ${absurl(url.context)}${child.icon16} urn:uuid:${child.id} ${xmldate(child.properties.modified)} - ${child.properties.description!""} + ${child.properties.description?xml!""} - ${child.properties.creator} - + ${child.properties.creator?xml} + diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 89ed40faf0..3a847ae50e 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -83,6 +83,10 @@ SYSTEM_PROPERTIES_MODE_NEVER + + + 6 + @@ -112,7 +116,7 @@ ${alfresco.jgroups.bind_address} - + ${alfresco.jgroups.bind_interface} @@ -459,6 +463,7 @@ alfresco.messages.content-filter-languages alfresco.messages.jbpm-engine-messages alfresco.messages.activiti-engine-messages + alfresco.messages.wdr-messages @@ -1033,12 +1038,18 @@ + + + + + + - opaquelocktoken - sharedLockTokens - lockDepth - lockScope + webdav:opaquelocktoken + webdav:sharedLockTokens + webdav:lockDepth + webdav:lockScope diff --git a/config/alfresco/dao/dao-context.xml b/config/alfresco/dao/dao-context.xml index 9bae33a44e..95721aa27f 100644 --- a/config/alfresco/dao/dao-context.xml +++ b/config/alfresco/dao/dao-context.xml @@ -60,6 +60,8 @@ + + diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.Dialect/AlfrescoPostCreate-JBPM-FK-indexes.sql b/config/alfresco/dbscripts/create/org.hibernate.dialect.Dialect/AlfrescoPostCreate-JBPM-FK-indexes.sql new file mode 100644 index 0000000000..c33e74115a --- /dev/null +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.Dialect/AlfrescoPostCreate-JBPM-FK-indexes.sql @@ -0,0 +1,109 @@ +-- +-- Title: Upgrade to V3.4 - Add indexes for jbpm foreign keys +-- Database: Generic +-- Since: V3.4 schema 4204 +-- Author: pavelyur +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +CREATE INDEX FK_ACTION_REFACT ON JBPM_ACTION (REFERENCEDACTION_); --(optional) +CREATE INDEX FK_CRTETIMERACT_TA ON JBPM_ACTION (TIMERACTION_); --(optional) +CREATE INDEX FK_ACTION_PROCDEF ON JBPM_ACTION (PROCESSDEFINITION_); --(optional) +CREATE INDEX FK_ACTION_EVENT ON JBPM_ACTION (EVENT_); --(optional) +CREATE INDEX FK_ACTION_ACTNDEL ON JBPM_ACTION (ACTIONDELEGATION_); --(optional) +CREATE INDEX FK_ACTION_EXPTHDL ON JBPM_ACTION(EXCEPTIONHANDLER_); --(optional) +CREATE INDEX FK_BYTEARR_FILDEF ON JBPM_BYTEARRAY (FILEDEFINITION_); --(optional) +CREATE INDEX FK_BYTEBLOCK_FILE ON JBPM_BYTEBLOCK (PROCESSFILE_); --(optional) +CREATE INDEX FK_COMMENT_TOKEN ON JBPM_COMMENT (TOKEN_); --(optional) +CREATE INDEX FK_COMMENT_TSK ON JBPM_COMMENT (TASKINSTANCE_); --(optional) +CREATE INDEX FK_DECCOND_DEC ON JBPM_DECISIONCONDITIONS (DECISION_); --(optional) +CREATE INDEX FK_DELEGATION_PRCD ON JBPM_DELEGATION (PROCESSDEFINITION_); --(optional) +CREATE INDEX FK_EVENT_PROCDEF ON JBPM_EVENT (PROCESSDEFINITION_); --(optional) +CREATE INDEX FK_EVENT_TRANS ON JBPM_EVENT (TRANSITION_); --(optional) +CREATE INDEX FK_EVENT_NODE ON JBPM_EVENT (NODE_); --(optional) +CREATE INDEX FK_EVENT_TASK ON JBPM_EVENT (TASK_); --(optional) +CREATE INDEX FK_JOB_PRINST ON JBPM_JOB (PROCESSINSTANCE_); --(optional) +CREATE INDEX FK_JOB_ACTION ON JBPM_JOB (ACTION_); --(optional) +CREATE INDEX FK_JOB_TOKEN ON JBPM_JOB (TOKEN_); --(optional) +CREATE INDEX FK_JOB_NODE ON JBPM_JOB (NODE_); --(optional) +CREATE INDEX FK_JOB_TSKINST ON JBPM_JOB (TASKINSTANCE_); --(optional) +CREATE INDEX FK_LOG_SOURCENODE ON JBPM_LOG (SOURCENODE_); --(optional) +CREATE INDEX FK_LOG_DESTNODE ON JBPM_LOG (DESTINATIONNODE_); --(optional) +CREATE INDEX FK_LOG_TOKEN ON JBPM_LOG (TOKEN_); --(optional) +CREATE INDEX FK_LOG_TRANSITION ON JBPM_LOG (TRANSITION_); --(optional) +CREATE INDEX FK_LOG_TASKINST ON JBPM_LOG (TASKINSTANCE_); --(optional) +CREATE INDEX FK_LOG_CHILDTOKEN ON JBPM_LOG (CHILD_); --(optional) +CREATE INDEX FK_LOG_OLDBYTES ON JBPM_LOG (OLDBYTEARRAY_); --(optional) +CREATE INDEX FK_LOG_SWIMINST ON JBPM_LOG (SWIMLANEINSTANCE_); --(optional) +CREATE INDEX FK_LOG_NEWBYTES ON JBPM_LOG (NEWBYTEARRAY_); --(optional) +CREATE INDEX FK_LOG_ACTION ON JBPM_LOG (ACTION_); --(optional) +CREATE INDEX FK_LOG_VARINST ON JBPM_LOG (VARIABLEINSTANCE_); --(optional) +CREATE INDEX FK_LOG_NODE ON JBPM_LOG (NODE_); --(optional) +CREATE INDEX FK_LOG_PARENT ON JBPM_LOG (PARENT_); --(optional) +CREATE INDEX FK_MODDEF_PROCDEF ON JBPM_MODULEDEFINITION (PROCESSDEFINITION_); --(optional) +CREATE INDEX FK_TSKDEF_START ON JBPM_MODULEDEFINITION (STARTTASK_); --(optional) +CREATE INDEX FK_MODINST_PRCINST ON JBPM_MODULEINSTANCE (PROCESSINSTANCE_); --(optional) +CREATE INDEX FK_TASKMGTINST_TMD ON JBPM_MODULEINSTANCE (TASKMGMTDEFINITION_); --(optional) +CREATE INDEX FK_DECISION_DELEG ON JBPM_NODE (DECISIONDELEGATION); --(optional) +CREATE INDEX FK_NODE_PROCDEF ON JBPM_NODE (PROCESSDEFINITION_); --(optional) +CREATE INDEX FK_NODE_ACTION ON JBPM_NODE (ACTION_); --(optional) +CREATE INDEX FK_PROCST_SBPRCDEF ON JBPM_NODE (SUBPROCESSDEFINITION_); --(optional) +CREATE INDEX FK_NODE_SCRIPT ON JBPM_NODE (SCRIPT_); --(optional) +CREATE INDEX FK_NODE_SUPERSTATE ON JBPM_NODE (SUPERSTATE_); --(optional) +CREATE INDEX FK_POOLEDACTOR_SLI ON JBPM_POOLEDACTOR (SWIMLANEINSTANCE_); --(optional) +CREATE INDEX FK_PROCDEF_STRTSTA ON JBPM_PROCESSDEFINITION (STARTSTATE_); --(optional) +CREATE INDEX FK_PROCIN_PROCDEF ON JBPM_PROCESSINSTANCE (PROCESSDEFINITION_); --(optional) +CREATE INDEX FK_PROCIN_ROOTTKN ON JBPM_PROCESSINSTANCE (ROOTTOKEN_); --(optional) +CREATE INDEX FK_PROCIN_SPROCTKN ON JBPM_PROCESSINSTANCE (SUPERPROCESSTOKEN_); --(optional) +CREATE INDEX FK_RTACTN_PROCINST ON JBPM_RUNTIMEACTION (PROCESSINSTANCE_); --(optional) +CREATE INDEX FK_RTACTN_ACTION ON JBPM_RUNTIMEACTION (ACTION_); --(optional) +CREATE INDEX FK_SWL_ASSDEL ON JBPM_SWIMLANE (ASSIGNMENTDELEGATION_); --(optional) +CREATE INDEX FK_SWL_TSKMGMTDEF ON JBPM_SWIMLANE (TASKMGMTDEFINITION_); --(optional) +CREATE INDEX FK_SWIMLANEINST_TM ON JBPM_SWIMLANEINSTANCE (TASKMGMTINSTANCE_); --(optional) +CREATE INDEX FK_SWIMLANEINST_SL ON JBPM_SWIMLANEINSTANCE (SWIMLANE_); --(optional) +CREATE INDEX FK_TASK_STARTST ON JBPM_TASK (STARTSTATE_); --(optional) +CREATE INDEX FK_TASK_PROCDEF ON JBPM_TASK (PROCESSDEFINITION_); --(optional) +CREATE INDEX FK_TASK_ASSDEL ON JBPM_TASK (ASSIGNMENTDELEGATION_); --(optional) +CREATE INDEX FK_TASK_SWIMLANE ON JBPM_TASK (SWIMLANE_); --(optional) +CREATE INDEX FK_TASK_TASKNODE ON JBPM_TASK (TASKNODE_); --(optional) +CREATE INDEX FK_TASK_TASKMGTDEF ON JBPM_TASK (TASKMGMTDEFINITION_); --(optional) +CREATE INDEX FK_TSK_TSKCTRL ON JBPM_TASK (TASKCONTROLLER_); --(optional) +CREATE INDEX FK_TASKACTPL_TSKI ON JBPM_TASKACTORPOOL (TASKINSTANCE_); --(optional) +CREATE INDEX FK_TSKACTPOL_PLACT ON JBPM_TASKACTORPOOL (POOLEDACTOR_); --(optional) +CREATE INDEX FK_TSKCTRL_DELEG ON JBPM_TASKCONTROLLER (TASKCONTROLLERDELEGATION_); --(optional) +CREATE INDEX FK_TSKINS_PRCINS ON JBPM_TASKINSTANCE (PROCINST_); --(optional) +CREATE INDEX FK_TASKINST_TMINST ON JBPM_TASKINSTANCE (TASKMGMTINSTANCE_); --(optional) +CREATE INDEX FK_TASKINST_TOKEN ON JBPM_TASKINSTANCE (TOKEN_); --(optional) +CREATE INDEX FK_TASKINST_SLINST ON JBPM_TASKINSTANCE (SWIMLANINSTANCE_); --(optional) +CREATE INDEX FK_TASKINST_TASK ON JBPM_TASKINSTANCE (TASK_); --(optional) +CREATE INDEX FK_TOKEN_SUBPI ON JBPM_TOKEN (SUBPROCESSINSTANCE_); --(optional) +CREATE INDEX FK_TOKEN_PROCINST ON JBPM_TOKEN (PROCESSINSTANCE_); --(optional) +CREATE INDEX FK_TOKEN_NODE ON JBPM_TOKEN (NODE_); --(optional) +CREATE INDEX FK_TOKEN_PARENT ON JBPM_TOKEN (PARENT_); --(optional) +CREATE INDEX FK_TKVARMAP_TOKEN ON JBPM_TOKENVARIABLEMAP (TOKEN_); --(optional) +CREATE INDEX FK_TKVARMAP_CTXT ON JBPM_TOKENVARIABLEMAP (CONTEXTINSTANCE_); --(optional) +CREATE INDEX FK_TRANSITION_FROM ON JBPM_TRANSITION (FROM_); --(optional) +CREATE INDEX FK_TRANS_PROCDEF ON JBPM_TRANSITION (PROCESSDEFINITION_); --(optional) +CREATE INDEX FK_TRANSITION_TO ON JBPM_TRANSITION (TO_); --(optional) +CREATE INDEX FK_VARACC_PROCST ON JBPM_VARIABLEACCESS (PROCESSSTATE_); --(optional) +CREATE INDEX FK_VARACC_SCRIPT ON JBPM_VARIABLEACCESS (SCRIPT_); --(optional) +CREATE INDEX FK_VARACC_TSKCTRL ON JBPM_VARIABLEACCESS (TASKCONTROLLER_); --(optional) +CREATE INDEX FK_VARINST_PRCINST ON JBPM_VARIABLEINSTANCE (PROCESSINSTANCE_); --(optional) +CREATE INDEX FK_VARINST_TKVARMP ON JBPM_VARIABLEINSTANCE (TOKENVARIABLEMAP_); --(optional) +CREATE INDEX FK_VARINST_TK ON JBPM_VARIABLEINSTANCE (TOKEN_); --(optional) +CREATE INDEX FK_BYTEINST_ARRAY ON JBPM_VARIABLEINSTANCE (BYTEARRAYVALUE_); --(optional) +CREATE INDEX FK_VAR_TSKINST ON JBPM_VARIABLEINSTANCE (TASKINSTANCE_); --(optional) + + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V3.4-JBPM-FK-indexes'; +INSERT INTO alf_applied_patch + (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) + VALUES + ( + 'patch.db-V3.4-JBPM-FK-indexes', 'Manually executed script upgrade to add FK indexes for JBPM', + 0, 4305, -1, 4306, null, 'UNKOWN', ${TRUE}, ${TRUE}, 'Script completed' + ); diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoPostCreate-JBPM-FK-indexes.sql b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoPostCreate-JBPM-FK-indexes.sql new file mode 100644 index 0000000000..3df4d6fd47 --- /dev/null +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoPostCreate-JBPM-FK-indexes.sql @@ -0,0 +1,22 @@ +-- +-- Title: Upgrade to V3.4 - Add indexes for jbpm foreign keys +-- Database: MySQL +-- Since: V3.4 schema 4204 +-- Author: pavelyur +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +-- do nothing for mysql + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V3.4-JBPM-FK-indexes'; +INSERT INTO alf_applied_patch + (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) + VALUES + ( + 'patch.db-V3.4-JBPM-FK-indexes', 'Manually executed script upgrade to add FK indexes for JBPM', + 0, 4305, -1, 4306, null, 'UNKOWN', ${TRUE}, ${TRUE}, 'Script completed' + ); diff --git a/config/alfresco/dbscripts/upgrade/3.4/org.hibernate.dialect.Dialect/AVM-rename-dupes.sql b/config/alfresco/dbscripts/upgrade/3.4/org.hibernate.dialect.Dialect/AVM-rename-dupes.sql index d3993eb90f..1e90dfaee7 100644 --- a/config/alfresco/dbscripts/upgrade/3.4/org.hibernate.dialect.Dialect/AVM-rename-dupes.sql +++ b/config/alfresco/dbscripts/upgrade/3.4/org.hibernate.dialect.Dialect/AVM-rename-dupes.sql @@ -27,17 +27,17 @@ FROM LOWER(ce.name) = entities.lname AND ce.child_id != entities.max_child_id; -UPDATE avm_child_entries - SET name = name || '-renamed.duplicate.mark-' || child_id || '.temp' +UPDATE avm_child_entries ce + SET name = ce.name || '-renamed.duplicate.mark-' || ce.child_id || '.temp' WHERE EXISTS (SELECT 1 FROM avm_tmp_child_entries tmp WHERE - parent_id = tmp.parent_id AND - name = tmp.name AND - child_id = tmp.child_id); + ce.parent_id = tmp.parent_id AND + ce.name = tmp.name AND + ce.child_id = tmp.child_id); --ASSIGN:update_count=value SELECT diff --git a/config/alfresco/dbscripts/upgrade/3.4/org.hibernate.dialect.Dialect/varchar-field-sizes-quadruple-increasing.sql b/config/alfresco/dbscripts/upgrade/3.4/org.hibernate.dialect.Dialect/varchar-field-sizes-quadruple-increasing.sql new file mode 100644 index 0000000000..29bd0ec2e6 --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/3.4/org.hibernate.dialect.Dialect/varchar-field-sizes-quadruple-increasing.sql @@ -0,0 +1,21 @@ +-- +-- Title: Increasing 'VARCHAR' field sizes quadruply for DB2 dialect +-- Database: Generic +-- Since: V3.4 +-- Author: Dmitry Velichkevich +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- +-- ALF-4300: DB2: Review schema (eg. VARCHAR columns) with respect to multi-byte support (when using DB2 / UTF-8) + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V3.4-VarcharFieldSizesQuadrupleIncreasing'; +INSERT INTO + alf_applied_patch + (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) +VALUES ( + 'patch.db-V3.4-VarcharFieldSizesQuadrupleIncreasing', 'Increasing VARCHAR field sizes quadruply for DB2 dialect V3.4', + 0, 4303, -1, 4304, null, 'UNKOWN', ${TRUE}, ${TRUE}, 'Script completed' +); diff --git a/config/alfresco/form-services-context.xml b/config/alfresco/form-services-context.xml index c65d96e587..09043d09e8 100644 --- a/config/alfresco/form-services-context.xml +++ b/config/alfresco/form-services-context.xml @@ -159,9 +159,8 @@ parent="baseFormProcessor"> - - workflow - + + - - task - + + diff --git a/config/alfresco/hibernate-context.xml b/config/alfresco/hibernate-context.xml index 6b7f56290c..b7122deeef 100644 --- a/config/alfresco/hibernate-context.xml +++ b/config/alfresco/hibernate-context.xml @@ -67,6 +67,7 @@ org/alfresco/repo/workflow/jbpm/jbpm.Join.hbm.xml org/jbpm/graph/node/State.hbm.xml org/jbpm/graph/node/TaskNode.hbm.xml + org/alfresco/repo/workflow/jbpm/jbpm.TaskNode.hbm.xml org/jbpm/context/def/ContextDefinition.hbm.xml org/jbpm/context/def/VariableAccess.hbm.xml org/jbpm/taskmgmt/def/TaskMgmtDefinition.hbm.xml @@ -98,6 +99,7 @@ org/jbpm/job/Timer.hbm.xml org/alfresco/repo/workflow/jbpm/jbpm.Timer.hbm.xml org/jbpm/job/ExecuteNodeJob.hbm.xml + org/alfresco/repo/workflow/jbpm/jbpm.ExecuteNodeJob.hbm.xml org/jbpm/job/ExecuteActionJob.hbm.xml org/jbpm/taskmgmt/exe/TaskMgmtInstance.hbm.xml org/jbpm/taskmgmt/exe/TaskInstance.hbm.xml diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/patch-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/patch-common-SqlMap.xml index 87273d4cea..6178837e5d 100644 --- a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/patch-common-SqlMap.xml +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/patch-common-SqlMap.xml @@ -79,6 +79,21 @@ + + + + + + + + + + + + + + + @@ -359,6 +374,138 @@ and (lower(ce.name) = entities.lname) order by parent_id, lower(name), child_id + ]]> + + + + + + + + + + + diff --git a/config/alfresco/invitation-service-context.xml b/config/alfresco/invitation-service-context.xml index 7d4bf14161..4cf49594c5 100644 --- a/config/alfresco/invitation-service-context.xml +++ b/config/alfresco/invitation-service-context.xml @@ -10,6 +10,7 @@ + @@ -70,4 +71,4 @@ - \ No newline at end of file + diff --git a/config/alfresco/jbpm-context.xml b/config/alfresco/jbpm-context.xml index 4c029ab307..96287e2982 100644 --- a/config/alfresco/jbpm-context.xml +++ b/config/alfresco/jbpm-context.xml @@ -6,7 +6,7 @@ - + + diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 9bb96341f4..02b637ca89 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -1878,7 +1878,7 @@ - + @@ -2760,4 +2760,48 @@ + + + + + + + + + classpath:alfresco/dbscripts/upgrade/3.4/${db.script.dialect}/varchar-field-sizes-quadruple-increasing.sql + + + + + patch.fixAclInheritance + patch.fixAclInheritance.description + 0 + 4304 + 4305 + + + + + + + + + + + + + + + + + patch.db-V3.4-JBPM-FK-indexes + patch.schemaUpgradeScript.description + 0 + 4305 + 4306 + + classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoPostCreate-JBPM-FK-indexes.sql + + + diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml index 7d80d56b5f..1f05ff018d 100644 --- a/config/alfresco/public-services-security-context.xml +++ b/config/alfresco/public-services-security-context.xml @@ -421,7 +421,7 @@ org.alfresco.service.cmr.model.FileFolderService.rename=ACL_NODE.0.sys:base.WriteProperties org.alfresco.service.cmr.model.FileFolderService.move=ACL_NODE.0.sys:base.DeleteNode,ACL_NODE.1.sys:base.CreateChildren org.alfresco.service.cmr.model.FileFolderService.moveFrom=ACL_NODE.0.sys:base.DeleteNode,ACL_NODE.2.sys:base.CreateChildren - org.alfresco.service.cmr.model.FileFolderService.copy=ACL_NODE.0.sys:base.ReadProperties,ACL_NODE.1.sys:base.CreateChildren + org.alfresco.service.cmr.model.FileFolderService.copy=ACL_NODE.0.sys:base.Read,ACL_NODE.1.sys:base.CreateChildren org.alfresco.service.cmr.model.FileFolderService.create=ACL_NODE.0.sys:base.CreateChildren org.alfresco.service.cmr.model.FileFolderService.delete=ACL_NODE.0.sys:base.DeleteNode org.alfresco.service.cmr.model.FileFolderService.getNamePath=ACL_NODE.1.sys:base.ReadProperties diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 234d9d21ff..224c4efef5 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -39,6 +39,9 @@ system.webdav.rootPath=${protocols.rootPath} # servlet allows unauthenticated deployment of new workflows. system.workflow.deployservlet.enabled=false +# Sets the location for the JBPM Configuration File +system.workflow.jbpm.config.location=classpath:org/alfresco/repo/workflow/jbpm/jbpm.cfg.xml + # ######################################### # # Index Recovery and Tracking Configuration # # ######################################### # diff --git a/config/alfresco/site-services-context.xml b/config/alfresco/site-services-context.xml index 932b701437..e39cd5e34d 100644 --- a/config/alfresco/site-services-context.xml +++ b/config/alfresco/site-services-context.xml @@ -108,6 +108,7 @@ + diff --git a/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml b/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml index a1478b7411..b8dcd9201c 100644 --- a/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml +++ b/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml @@ -63,6 +63,7 @@ + diff --git a/config/alfresco/subsystems/fileServers/default/file-servers-context.xml b/config/alfresco/subsystems/fileServers/default/file-servers-context.xml index 53cc504555..5ce1ff1b93 100644 --- a/config/alfresco/subsystems/fileServers/default/file-servers-context.xml +++ b/config/alfresco/subsystems/fileServers/default/file-servers-context.xml @@ -49,6 +49,18 @@ + + + + + + + + + + + + @@ -198,11 +210,6 @@ ${ftp.port} - - - ${ftp.ipv6.enabled} - - diff --git a/config/alfresco/subsystems/fileServers/default/file-servers.properties b/config/alfresco/subsystems/fileServers/default/file-servers.properties index 9c2ed117aa..b6dc2e0b6f 100644 --- a/config/alfresco/subsystems/fileServers/default/file-servers.properties +++ b/config/alfresco/subsystems/fileServers/default/file-servers.properties @@ -9,6 +9,11 @@ filesystem.rootPath=${protocols.rootPath} # File name patterns that trigger rename shuffle detection filesystem.renameShufflePattern=(.*\\.tmp)|(.*\\.wbk)|(.*\\.bak)|(.*\\~) +# Should we ever set the read only flag on folders? This may cause problematic +# behaviour in Windows clients. See ALF-6727. +filesystem.setReadOnlyFlagOnFolders=false + + ### CIFS Server Configuration ### cifs.enabled=true cifs.serverName=${localname}A @@ -47,7 +52,6 @@ cifs.sessionDebug= ### FTP Server Configuration ### ftp.enabled=true ftp.port=21 -ftp.ipv6.enabled=false # FTP data port range, a value of 0:0 disables the data port range and will use the next available port # Valid range is 1024-65535 @@ -68,6 +72,8 @@ ftp.sessionDebug= ### NFS Server Configuration ### nfs.enabled=false +# NodeMonitor to update cache of NFS server on nodes renaming and deleting not through NFS protocol +nfs.nodeMinitor.enabled=${nfs.enabled} # Mount/NFS server ports, 0 will allocate next available port nfs.mountServerPort=0 nfs.nfsServerPort=2049 diff --git a/config/alfresco/network-protocol-context.xml b/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml similarity index 90% rename from config/alfresco/network-protocol-context.xml rename to config/alfresco/subsystems/fileServers/default/network-protocol-context.xml index c49a630854..14ba2e70b9 100644 --- a/config/alfresco/network-protocol-context.xml +++ b/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml @@ -67,6 +67,8 @@ + + @@ -84,6 +86,12 @@ ${server.transaction.allow-writes} + ${filesystem.setReadOnlyFlagOnFolders} + + + {http://www.alfresco.org/model/forum/1.0}forum + + diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index e8c8ea905d..57385f4f17 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=4303 +version.schema=4306 diff --git a/config/alfresco/wcm-services-context.xml b/config/alfresco/wcm-services-context.xml index 74b56d21b1..1adc22fbcf 100644 --- a/config/alfresco/wcm-services-context.xml +++ b/config/alfresco/wcm-services-context.xml @@ -45,6 +45,7 @@ + diff --git a/config/alfresco/workflow/invitation-moderated-workflow-messages_ja.properties b/config/alfresco/workflow/invitation-moderated-workflow-messages_ja.properties index 96a13ea0a2..0acdf2b107 100755 --- a/config/alfresco/workflow/invitation-moderated-workflow-messages_ja.properties +++ b/config/alfresco/workflow/invitation-moderated-workflow-messages_ja.properties @@ -5,7 +5,7 @@ # imwf_invitation-moderated.workflow.title=\u62db\u5f85 - \u30e2\u30c7\u30ec\u30fc\u30c8\u6e08 -imwf_invitation-moderated.workflow.description=Web\u30fb\u30b5\u30a4\u30c8\u306a\u3069\u306e\u30ea\u30bd\u30fc\u30b9\u306b\u30e2\u30c7\u30ec\u30fc\u30c8\u3055\u308c\u305f\u62db\u5f85 +imwf_invitation-moderated.workflow.description=Web\u30b5\u30a4\u30c8\u306a\u3069\u306e\u30ea\u30bd\u30fc\u30b9\u306b\u30e2\u30c7\u30ec\u30fc\u30c8\u3055\u308c\u305f\u62db\u5f85 imwf_invitation-moderated-workflow-model.type.imwf_moderatedInvitationReviewTask.title=\u30e2\u30c7\u30ec\u30fc\u30c8\u3055\u308c\u305f\u30b5\u30a4\u30c8\u62db\u5f85 imwf_invitation-moderated-workflow-model.type.imwf_moderatedInvitationReviewTask.description=\u30e2\u30c7\u30ec\u30fc\u30c8\u3055\u308c\u305f\u62db\u5f85\u306e\u958b\u59cb diff --git a/config/alfresco/workflow/wcm-workflow-messages_ja.properties b/config/alfresco/workflow/wcm-workflow-messages_ja.properties index de5f013b35..823be36729 100755 --- a/config/alfresco/workflow/wcm-workflow-messages_ja.properties +++ b/config/alfresco/workflow/wcm-workflow-messages_ja.properties @@ -4,7 +4,7 @@ # Submit Workflow # -wcmwf_submit.workflow.title=Web\u30fb\u30b5\u30a4\u30c8\u63d0\u51fa +wcmwf_submit.workflow.title=Web\u30b5\u30a4\u30c8\u63d0\u51fa wcmwf_submit.workflow.description=\u627f\u8a8d\u7528\u306b\u5909\u66f4\u3092\u9001\u4fe1 wcmwf_submit.node.verifybrokenlinks.transition.abort.title=\u63d0\u51fa\u306e\u505c\u6b62 wcmwf_submit.node.verifybrokenlinks.transition.abort.description=\u63d0\u51fa\u306e\u505c\u6b62 @@ -27,7 +27,7 @@ wcmwf_submit.node.submitpending.transition.cancel.description=\u63d0\u51fa\u306e wcmwf_submit.node.submitpending.transition.launch.title=\u4eca\u3059\u3050\u9001\u4fe1 wcmwf_submit.node.submitpending.transition.launch.description=\u4eca\u3059\u3050\u9001\u4fe1 -wcmwf_submitdirect.workflow.title=Web\u30fb\u30b5\u30a4\u30c8\u63d0\u51fa\uff08\u76f4\u63a5\uff09 +wcmwf_submitdirect.workflow.title=Web\u30b5\u30a4\u30c8\u63d0\u51fa\uff08\u76f4\u63a5\uff09 wcmwf_submitdirect.workflow.description=\u30b9\u30c6\u30fc\u30b8\u30f3\u30b0\u30fb\u30b5\u30f3\u30c9\u30dc\u30c3\u30af\u30b9\u306b\u5909\u66f4\u3092\u76f4\u63a5\u9001\u4fe1 diff --git a/config/test/alfresco/model/testWcmModel.xml b/config/test/alfresco/model/testWcmModel.xml new file mode 100644 index 0000000000..61f5238c96 --- /dev/null +++ b/config/test/alfresco/model/testWcmModel.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + d:date + true + + + + + + diff --git a/config/test/alfresco/wcm-template-node-test-context.xml b/config/test/alfresco/wcm-template-node-test-context.xml new file mode 100644 index 0000000000..d115215c07 --- /dev/null +++ b/config/test/alfresco/wcm-template-node-test-context.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + test/alfresco/model/testWcmModel.xml + + + + + diff --git a/source/java/org/alfresco/cmis/CMISRelationshipDirectionEnum.java b/source/java/org/alfresco/cmis/CMISRelationshipDirectionEnum.java index 5ca5151d9c..39b8f432a3 100644 --- a/source/java/org/alfresco/cmis/CMISRelationshipDirectionEnum.java +++ b/source/java/org/alfresco/cmis/CMISRelationshipDirectionEnum.java @@ -27,7 +27,7 @@ public enum CMISRelationshipDirectionEnum implements EnumLabel { SOURCE("source"), TARGET("target"), - BOTH("both"); + EITHER("either"); private String label; diff --git a/source/java/org/alfresco/cmis/CMISServices.java b/source/java/org/alfresco/cmis/CMISServices.java index 30d8cf3323..91a16a65f5 100644 --- a/source/java/org/alfresco/cmis/CMISServices.java +++ b/source/java/org/alfresco/cmis/CMISServices.java @@ -673,7 +673,7 @@ public interface CMISServices */ public void deleteObject(String objectId, boolean allVersions) throws CMISConstraintException, CMISVersioningException, CMISObjectNotFoundException, CMISInvalidArgumentException, - CMISPermissionDeniedException, CMISRuntimeException; + CMISPermissionDeniedException, CMISRuntimeException, CMISServiceException; /** * Adds a secondary child association to an object from a folder. diff --git a/source/java/org/alfresco/cmis/dictionary/CMISBasePropertyDefinition.java b/source/java/org/alfresco/cmis/dictionary/CMISBasePropertyDefinition.java index dc080973a4..60163cb066 100644 --- a/source/java/org/alfresco/cmis/dictionary/CMISBasePropertyDefinition.java +++ b/source/java/org/alfresco/cmis/dictionary/CMISBasePropertyDefinition.java @@ -50,6 +50,7 @@ import org.alfresco.service.cmr.dictionary.Constraint; import org.alfresco.service.cmr.dictionary.ConstraintDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.util.ISO9075; /** * CMIS Property Definition @@ -109,7 +110,7 @@ public class CMISBasePropertyDefinition implements CMISPropertyDefinition, Seria { this.propertyId = propertyId; this.typeDef = typeDef; - queryName = cmisMapping.buildPrefixEncodedString(propertyId.getQName()); + queryName = ISO9075.encodeSQL(cmisMapping.buildPrefixEncodedString(propertyId.getQName())); displayName = (propDef.getTitle() != null) ? propDef.getTitle() : propertyId.getId(); description = propDef.getDescription() != null ? propDef.getDescription() : displayName; propertyType = cmisMapping.getDataType(propDef.getDataType()); diff --git a/source/java/org/alfresco/cmis/dictionary/CMISDocumentTypeDefinition.java b/source/java/org/alfresco/cmis/dictionary/CMISDocumentTypeDefinition.java index bce1e97282..2d250a7659 100644 --- a/source/java/org/alfresco/cmis/dictionary/CMISDocumentTypeDefinition.java +++ b/source/java/org/alfresco/cmis/dictionary/CMISDocumentTypeDefinition.java @@ -25,6 +25,7 @@ import org.alfresco.cmis.CMISTypeId; import org.alfresco.cmis.mapping.CMISMapping; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.namespace.QName; +import org.alfresco.util.ISO9075; /** @@ -69,7 +70,7 @@ public class CMISDocumentTypeDefinition extends CMISAbstractTypeDefinition } else { - objectTypeQueryName = cmisMapping.buildPrefixEncodedString(typeId.getQName()); + objectTypeQueryName = ISO9075.encodeSQL(cmisMapping.buildPrefixEncodedString(typeId.getQName())); if (cmisMapping.isValidCmisDocument(parentQName)) { parentTypeId = cmisMapping.getCmisTypeId(CMISScope.DOCUMENT, parentQName); diff --git a/source/java/org/alfresco/cmis/dictionary/CMISFolderTypeDefinition.java b/source/java/org/alfresco/cmis/dictionary/CMISFolderTypeDefinition.java index 57f3964de1..0713768454 100644 --- a/source/java/org/alfresco/cmis/dictionary/CMISFolderTypeDefinition.java +++ b/source/java/org/alfresco/cmis/dictionary/CMISFolderTypeDefinition.java @@ -25,6 +25,7 @@ import org.alfresco.cmis.mapping.CMISMapping; import org.alfresco.model.ContentModel; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.namespace.QName; +import org.alfresco.util.ISO9075; /** @@ -63,7 +64,7 @@ public class CMISFolderTypeDefinition extends CMISAbstractTypeDefinition } else { - objectTypeQueryName = cmisMapping.buildPrefixEncodedString(typeId.getQName()); + objectTypeQueryName = ISO9075.encodeSQL(cmisMapping.buildPrefixEncodedString(typeId.getQName())); if (cmisMapping.isValidCmisFolder(parentQName)) { parentTypeId = cmisMapping.getCmisTypeId(CMISScope.FOLDER, parentQName); diff --git a/source/java/org/alfresco/cmis/dictionary/CMISObjectTypeDefinition.java b/source/java/org/alfresco/cmis/dictionary/CMISObjectTypeDefinition.java index 0fe32d040d..6486479944 100644 --- a/source/java/org/alfresco/cmis/dictionary/CMISObjectTypeDefinition.java +++ b/source/java/org/alfresco/cmis/dictionary/CMISObjectTypeDefinition.java @@ -27,6 +27,7 @@ import org.alfresco.cmis.mapping.CMISMapping; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.ISO9075; /** @@ -52,7 +53,7 @@ public class CMISObjectTypeDefinition extends CMISAbstractTypeDefinition // Object type properties objectTypeId = typeId; - objectTypeQueryName = cmisMapping.buildPrefixEncodedString(typeId.getQName()); + objectTypeQueryName = ISO9075.encodeSQL(cmisMapping.buildPrefixEncodedString(typeId.getQName())); if (cmisClassDef != null) { diff --git a/source/java/org/alfresco/cmis/dictionary/CMISPolicyTypeDefinition.java b/source/java/org/alfresco/cmis/dictionary/CMISPolicyTypeDefinition.java index 12dd170896..1114227abf 100644 --- a/source/java/org/alfresco/cmis/dictionary/CMISPolicyTypeDefinition.java +++ b/source/java/org/alfresco/cmis/dictionary/CMISPolicyTypeDefinition.java @@ -31,6 +31,7 @@ import org.alfresco.cmis.mapping.CMISMapping; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.ISO9075; /** @@ -68,7 +69,7 @@ public class CMISPolicyTypeDefinition extends CMISAbstractTypeDefinition } else { - objectTypeQueryName = cmisMapping.buildPrefixEncodedString(typeId.getQName()); + objectTypeQueryName = ISO9075.encodeSQL(cmisMapping.buildPrefixEncodedString(typeId.getQName())); parentTypeId = CMISDictionaryModel.POLICY_TYPE_ID; } description = cmisClassDef.getDescription() != null ? cmisClassDef.getDescription() : displayName; diff --git a/source/java/org/alfresco/cmis/dictionary/CMISRelationshipTypeDefinition.java b/source/java/org/alfresco/cmis/dictionary/CMISRelationshipTypeDefinition.java index c38eb13883..57f1deafef 100644 --- a/source/java/org/alfresco/cmis/dictionary/CMISRelationshipTypeDefinition.java +++ b/source/java/org/alfresco/cmis/dictionary/CMISRelationshipTypeDefinition.java @@ -36,6 +36,7 @@ import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.ISO9075; /** @@ -95,7 +96,7 @@ public class CMISRelationshipTypeDefinition extends CMISAbstractTypeDefinition { creatable = true; displayName = (assocDef.getTitle() != null) ? assocDef.getTitle() : typeId.getId(); - objectTypeQueryName = cmisMapping.buildPrefixEncodedString(typeId.getQName()); + objectTypeQueryName = ISO9075.encodeSQL(cmisMapping.buildPrefixEncodedString(typeId.getQName())); parentTypeId = CMISDictionaryModel.RELATIONSHIP_TYPE_ID; description = assocDef.getDescription() != null ? assocDef.getDescription() : displayName; diff --git a/source/java/org/alfresco/cmis/dictionary/CMISStrictDictionaryService.java b/source/java/org/alfresco/cmis/dictionary/CMISStrictDictionaryService.java index 96aca1a748..05ee07c46d 100644 --- a/source/java/org/alfresco/cmis/dictionary/CMISStrictDictionaryService.java +++ b/source/java/org/alfresco/cmis/dictionary/CMISStrictDictionaryService.java @@ -22,6 +22,7 @@ import java.util.Collection; import org.alfresco.cmis.CMISScope; import org.alfresco.cmis.CMISTypeId; +import org.alfresco.cmis.mapping.CMISMapping; import org.alfresco.model.ContentModel; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; @@ -67,6 +68,8 @@ public class CMISStrictDictionaryService extends CMISAbstractDictionaryService CMISTypeId typeId = cmisMapping.getCmisTypeId(classQName); if (typeId == null) continue; + if (typeId.getScope() == CMISScope.RELATIONSHIP) + continue; // create appropriate kind of type definition ClassDefinition classDef = dictionaryService.getClass(cmisMapping.getCmisType(typeId.getQName())); @@ -80,11 +83,6 @@ public class CMISStrictDictionaryService extends CMISAbstractDictionaryService boolean isSystem = dictionaryService.isSubClass(classDef.getName(), ContentModel.TYPE_SYSTEM_FOLDER); objectTypeDef = new CMISFolderTypeDefinition(cmisMapping, typeId, classDef, isSystem); } - else if (typeId.getScope() == CMISScope.RELATIONSHIP) - { - AssociationDefinition assocDef = dictionaryService.getAssociation(classQName); - objectTypeDef = new CMISRelationshipTypeDefinition(cmisMapping, typeId, classDef, assocDef); - } else if (typeId.getScope() == CMISScope.POLICY) { objectTypeDef = new CMISPolicyTypeDefinition(cmisMapping, typeId, classDef); @@ -106,15 +104,20 @@ public class CMISStrictDictionaryService extends CMISAbstractDictionaryService */ private void createAssocDefs(DictionaryRegistry registry, Collection classQNames) { + CMISTypeId typeId = cmisMapping.getCmisTypeId(CMISScope.RELATIONSHIP, CMISMapping.RELATIONSHIP_QNAME); + ClassDefinition classDef = dictionaryService.getClass(cmisMapping.getCmisType(typeId.getQName())); + CMISAbstractTypeDefinition objectTypeDef = new CMISRelationshipTypeDefinition(cmisMapping, typeId, classDef, null); + registry.registerTypeDefinition(objectTypeDef); + for (QName classQName : classQNames) { if (!cmisMapping.isValidCmisRelationship(classQName)) continue; // create appropriate kind of type definition - CMISTypeId typeId = cmisMapping.getCmisTypeId(CMISScope.RELATIONSHIP, classQName); + typeId = cmisMapping.getCmisTypeId(CMISScope.RELATIONSHIP, classQName); AssociationDefinition assocDef = dictionaryService.getAssociation(classQName); - CMISAbstractTypeDefinition objectTypeDef = new CMISRelationshipTypeDefinition(cmisMapping, typeId, null, assocDef); + objectTypeDef = new CMISRelationshipTypeDefinition(cmisMapping, typeId, null, assocDef); registry.registerTypeDefinition(objectTypeDef); } diff --git a/source/java/org/alfresco/cmis/mapping/CMISMapping.java b/source/java/org/alfresco/cmis/mapping/CMISMapping.java index 13c129950d..8891a654e8 100644 --- a/source/java/org/alfresco/cmis/mapping/CMISMapping.java +++ b/source/java/org/alfresco/cmis/mapping/CMISMapping.java @@ -23,7 +23,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -238,7 +237,7 @@ public class CMISMapping implements InitializingBean registerEvaluator(CMISScope.DOCUMENT, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_GET_ACL, PermissionService.READ_PERMISSIONS)); registerEvaluator(CMISScope.DOCUMENT, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_APPLY_ACL, PermissionService.CHANGE_PERMISSIONS)); - registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_DELETE_OBJECT, PermissionService.DELETE_NODE)); + registerEvaluator(CMISScope.FOLDER, new RootActionEvaluator(new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_DELETE_OBJECT, PermissionService.DELETE_NODE), false)); registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_UPDATE_PROPERTIES, PermissionService.WRITE_PROPERTIES)); registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_GET_FOLDER_TREE, PermissionService.READ_CHILDREN)); registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_GET_PROPERTIES, PermissionService.READ_PROPERTIES)); @@ -247,7 +246,7 @@ public class CMISMapping implements InitializingBean registerEvaluator(CMISScope.FOLDER, new ParentActionEvaluator(new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_GET_FOLDER_PARENT, PermissionService.READ_PERMISSIONS))); registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_GET_DESCENDANTS, PermissionService.READ_CHILDREN)); // Is CAN_MOVE_OBJECT correct mapping? - registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_MOVE_OBJECT, PermissionService.DELETE_NODE)); + registerEvaluator(CMISScope.FOLDER, new RootActionEvaluator(new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_MOVE_OBJECT, PermissionService.DELETE_NODE), false)); registerEvaluator(CMISScope.FOLDER, new FixedValueActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_APPLY_POLICY, false)); registerEvaluator(CMISScope.FOLDER, new FixedValueActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_GET_APPLIED_POLICIES, true)); registerEvaluator(CMISScope.FOLDER, new FixedValueActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_REMOVE_POLICY, false)); @@ -255,7 +254,7 @@ public class CMISMapping implements InitializingBean registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_CREATE_DOCUMENT, PermissionService.CREATE_CHILDREN)); registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_CREATE_FOLDER, PermissionService.CREATE_CHILDREN)); registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_CREATE_RELATIONSHIP, PermissionService.CREATE_ASSOCIATIONS)); - registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_DELETE_TREE, PermissionService.DELETE_NODE)); + registerEvaluator(CMISScope.FOLDER, new RootActionEvaluator(new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_DELETE_TREE, PermissionService.DELETE_NODE), false)); registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_GET_ACL, PermissionService.READ_PERMISSIONS)); registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_APPLY_ACL, PermissionService.CHANGE_PERMISSIONS)); @@ -406,14 +405,14 @@ public class CMISMapping implements InitializingBean { return getCmisTypeId(CMISScope.FOLDER, classQName); } - if (isValidCmisRelationship(classQName)) - { - return getCmisTypeId(CMISScope.RELATIONSHIP, classQName); - } if (isValidCmisPolicy(classQName)) { return getCmisTypeId(CMISScope.POLICY, classQName); } + if (isValidCmisRelationship(classQName)) + { + return getCmisTypeId(CMISScope.RELATIONSHIP, classQName); + } return null; } @@ -560,14 +559,6 @@ public class CMISMapping implements InitializingBean { return false; } - if (!isValidCmisDocumentOrFolder(getCmisType(associationDefinition.getSourceClass().getName()))) - { - return false; - } - if (!isValidCmisDocumentOrFolder(getCmisType(associationDefinition.getTargetClass().getName()))) - { - return false; - } return true; } diff --git a/source/java/org/alfresco/cmis/mapping/CMISServicesImpl.java b/source/java/org/alfresco/cmis/mapping/CMISServicesImpl.java index d319b3e880..5025a4e6a0 100644 --- a/source/java/org/alfresco/cmis/mapping/CMISServicesImpl.java +++ b/source/java/org/alfresco/cmis/mapping/CMISServicesImpl.java @@ -692,11 +692,11 @@ public class CMISServicesImpl implements CMISServices, ApplicationContextAware, // retrieve associations List assocs = new ArrayList(); - if (direction == CMISRelationshipDirectionEnum.SOURCE || direction == CMISRelationshipDirectionEnum.BOTH) + if (direction == CMISRelationshipDirectionEnum.SOURCE || direction == CMISRelationshipDirectionEnum.EITHER) { assocs.addAll(nodeService.getTargetAssocs(node, RegexQNamePattern.MATCH_ALL)); } - if (direction == CMISRelationshipDirectionEnum.TARGET || direction == CMISRelationshipDirectionEnum.BOTH) + if (direction == CMISRelationshipDirectionEnum.TARGET || direction == CMISRelationshipDirectionEnum.EITHER) { assocs.addAll(nodeService.getSourceAssocs(node, RegexQNamePattern.MATCH_ALL)); } @@ -709,12 +709,16 @@ public class CMISServicesImpl implements CMISServices, ApplicationContextAware, for (AssociationRef assoc : assocs) { CMISTypeDefinition assocTypeDef = cmisDictionaryService.findTypeForClass(assoc.getTypeQName(), CMISScope.RELATIONSHIP); - if (assocTypeDef != null) + QName sourceTypeDef = nodeService.getType(assoc.getSourceRef()); + QName targetTypeDef = nodeService.getType(assoc.getTargetRef()); + if (assocTypeDef == null || cmisDictionaryService.findTypeForClass(sourceTypeDef) == null || + cmisDictionaryService.findTypeForClass(targetTypeDef) == null) { - if (assocTypeDef.equals(relDef) || (subRelDefs != null && subRelDefs.contains(assocTypeDef))) - { - filteredAssocs.add(assoc); - } + continue; + } + if (assocTypeDef.equals(relDef) || (subRelDefs != null && subRelDefs.contains(assocTypeDef))) + { + filteredAssocs.add(assoc); } } @@ -1466,7 +1470,7 @@ public class CMISServicesImpl implements CMISServices, ApplicationContextAware, */ public void deleteObject(String objectId, boolean allVersions) throws CMISConstraintException, CMISVersioningException, CMISObjectNotFoundException, CMISInvalidArgumentException, - CMISPermissionDeniedException, CMISRuntimeException + CMISPermissionDeniedException, CMISRuntimeException, CMISServiceException { try { @@ -1533,6 +1537,10 @@ public class CMISServicesImpl implements CMISServices, ApplicationContextAware, // Attempt to delete the node nodeService.deleteNode(nodeRef); } + catch (CMISServiceException e) + { + throw e; + } catch (AccessDeniedException e) { throw new CMISPermissionDeniedException(e); diff --git a/source/java/org/alfresco/cmis/mapping/RootActionEvaluator.java b/source/java/org/alfresco/cmis/mapping/RootActionEvaluator.java new file mode 100644 index 0000000000..37791f8897 --- /dev/null +++ b/source/java/org/alfresco/cmis/mapping/RootActionEvaluator.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.cmis.mapping; + +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Action Evaluator which evaluates on whether node is root or not + * + * @author davidc + */ +public class RootActionEvaluator extends AbstractActionEvaluator +{ + private AbstractActionEvaluator evaluator; + private boolean allow; + + /** + * Construct + * + * @param serviceRegistry + * @param action + */ + protected RootActionEvaluator(AbstractActionEvaluator evaluator, boolean allow) + { + super(evaluator.getServiceRegistry(), evaluator.getAction()); + this.evaluator = evaluator; + this.allow = allow; + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.CMISActionEvaluator#isAllowed(org.alfresco.service.cmr.repository.NodeRef) + */ + public boolean isAllowed(NodeRef nodeRef) + { + if (nodeRef.equals(getServiceRegistry().getCMISService().getDefaultRootNodeRef())) + { + return allow; + } + return evaluator.isAllowed(nodeRef); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + builder.append("RootActionEvaluator[evaluator=").append(evaluator).append(",allow=").append(allow).append("]"); + return builder.toString(); + } +} + diff --git a/source/java/org/alfresco/cmis/search/CMIS-query-test-model.xml b/source/java/org/alfresco/cmis/search/CMIS-query-test-model.xml index 2ca70e9f2e..83a7853ac5 100644 --- a/source/java/org/alfresco/cmis/search/CMIS-query-test-model.xml +++ b/source/java/org/alfresco/cmis/search/CMIS-query-test-model.xml @@ -292,6 +292,30 @@ Extended Folder cm:folder + + + Type that requires encoding + cm:content + true + + + d:text + false + false + + true + both + + + + + + + Type that requires encoding + true + + + \ No newline at end of file diff --git a/source/java/org/alfresco/cmis/search/CmisFunctionEvaluationContext.java b/source/java/org/alfresco/cmis/search/CmisFunctionEvaluationContext.java index c762445198..57c5faecec 100644 --- a/source/java/org/alfresco/cmis/search/CmisFunctionEvaluationContext.java +++ b/source/java/org/alfresco/cmis/search/CmisFunctionEvaluationContext.java @@ -20,6 +20,7 @@ package org.alfresco.cmis.search; import java.io.Serializable; import java.util.Collection; +import java.util.HashSet; import java.util.Map; import org.alfresco.cmis.CMISCardinalityEnum; @@ -38,6 +39,7 @@ import org.alfresco.repo.search.impl.querymodel.QueryModelException; import org.alfresco.repo.search.impl.querymodel.Selector; import org.alfresco.repo.search.impl.querymodel.impl.functions.Lower; import org.alfresco.repo.search.impl.querymodel.impl.functions.Upper; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.apache.lucene.queryParser.ParseException; @@ -48,6 +50,8 @@ import org.apache.lucene.search.Query; */ public class CmisFunctionEvaluationContext implements FunctionEvaluationContext { + private static HashSet EXPOSED_FIELDS = new HashSet(); + public static CMISScope[] STRICT_SCOPES = new CMISScope[] { CMISScope.DOCUMENT, CMISScope.FOLDER }; public static CMISScope[] ALFRESCO_SCOPES = new CMISScope[] { CMISScope.DOCUMENT, CMISScope.FOLDER, CMISScope.POLICY }; @@ -64,6 +68,54 @@ public class CmisFunctionEvaluationContext implements FunctionEvaluationContext private Float score; + static + { + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_PATH); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_TEXT); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_ID); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_ISROOT); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_ISNODE); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_TX); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_PARENT); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_PRIMARYPARENT); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_QNAME); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_CLASS); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_TYPE); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_EXACTTYPE); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_ASPECT); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_EXACTASPECT); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_ALL); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_ISUNSET); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_ISNULL); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_ISNOTNULL); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_FTSSTATUS); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_ASSOCTYPEQNAME); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_PRIMARYASSOCTYPEQNAME); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_DBID); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_TAG); + + + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.ANY.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.ASSOC_REF.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.BOOLEAN.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.CATEGORY.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.CHILD_ASSOC_REF.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.CONTENT.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.DATE.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.DATETIME.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.DOUBLE.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.FLOAT.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.INT.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.LOCALE.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.LONG.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.MLTEXT.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.NODE_REF.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.PATH.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.PERIOD.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.QNAME.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.TEXT.getLocalName()); + } + /** * @param nodeRefs * the nodeRefs to set @@ -363,7 +415,14 @@ public class CmisFunctionEvaluationContext implements FunctionEvaluationContext CMISPropertyDefinition propDef = cmisDictionaryService.findPropertyByQueryName(propertyName); if (propDef == null) { - throw new CMISQueryException("Unknown column/property " + propertyName); + if (EXPOSED_FIELDS.contains(propertyName)) + { + return; + } + else + { + throw new CMISQueryException("Unknown column/property " + propertyName); + } } CMISTypeDefinition typeDef = cmisDictionaryService.findTypeForClass(selector.getType(), validScopes); diff --git a/source/java/org/alfresco/cmis/search/QueryTest.java b/source/java/org/alfresco/cmis/search/QueryTest.java index 7373384591..e38f7645e4 100644 --- a/source/java/org/alfresco/cmis/search/QueryTest.java +++ b/source/java/org/alfresco/cmis/search/QueryTest.java @@ -96,6 +96,7 @@ import org.antlr.runtime.CommonTokenStream; import org.antlr.runtime.RecognitionException; import org.antlr.runtime.tree.CommonTree; import org.alfresco.util.CachingDateFormat; +import org.alfresco.util.ISO9075; import org.springframework.extensions.surf.util.I18NUtil; /** @@ -105,6 +106,14 @@ public class QueryTest extends BaseCMISTest { private static final String TEST_NAMESPACE = "http://www.alfresco.org/test/cmis-query-test"; + + + QName typeThatRequiresEncoding = QName.createQName(TEST_NAMESPACE, "type-that-requires-encoding"); + + QName aspectThatRequiresEncoding = QName.createQName(TEST_NAMESPACE, "aspect-that-requires-encoding"); + + QName propertyThatRequiresEncoding = QName.createQName(TEST_NAMESPACE, "property-that-requires-encoding"); + QName extendedContent = QName.createQName(TEST_NAMESPACE, "extendedContent"); QName singleTextBoth = QName.createQName(TEST_NAMESPACE, "singleTextBoth"); @@ -583,6 +592,15 @@ public class QueryTest extends BaseCMISTest } + public void testEncodingOfTypeAndPropertyNames() + { + addTypeTestDataModel(); + assertNotNull("Type not found by query name "+ISO9075.encodeSQL(typeThatRequiresEncoding.toPrefixString(namespaceService)), cmisDictionaryService.findTypeByQueryName(ISO9075.encodeSQL(typeThatRequiresEncoding.toPrefixString(namespaceService)))); + assertNotNull("Aspect not found by query name "+ISO9075.encodeSQL(aspectThatRequiresEncoding.toPrefixString(namespaceService)), cmisDictionaryService.findTypeByQueryName(ISO9075.encodeSQL(aspectThatRequiresEncoding.toPrefixString(namespaceService)))); + assertNotNull("Prpo not found by query name "+ISO9075.encodeSQL(propertyThatRequiresEncoding.toPrefixString(namespaceService)), cmisDictionaryService.findPropertyByQueryName(ISO9075.encodeSQL(propertyThatRequiresEncoding.toPrefixString(namespaceService)))); + + } + public void test_ALLOWED_CHILD_OBJECT_TYPES() throws Exception { CMISQueryOptions options = new CMISQueryOptions("SELECT * FROM cmis:Folder", rootNodeRef.getStoreRef()); @@ -3290,6 +3308,9 @@ public class QueryTest extends BaseCMISTest testQuery("SELECT * FROM cmis:document WHERE CONTAINS('\\'quick\\'')", 1, false, "cmis:objectId", new String(), false); testExtendedQuery("SELECT * FROM cmis:document D WHERE CONTAINS(D, 'cmis:name:\\'Tutorial\\'')", 1, false, "cmis:objectId", new String(), false); testExtendedQuery("SELECT cmis:name as BOO FROM cmis:document D WHERE CONTAINS('BOO:\\'Tutorial\\'')", 1, false, "cmis:objectId", new String(), false); + testExtendedQuery("SELECT * FROM cmis:document D WHERE CONTAINS('TEXT:\\'zebra\\'')", doc_count-1, false, "cmis:objectId", new String(), false); + testExtendedQuery("SELECT * FROM cmis:document D WHERE CONTAINS('ALL:\\'zebra\\'')", doc_count-1, false, "cmis:objectId", new String(), false); + testExtendedQuery("SELECT * FROM cmis:document D WHERE CONTAINS('d:content:\\'zebra\\'')", doc_count-1, false, "cmis:objectId", new String(), false); } public void testScoreValues() @@ -3823,6 +3844,23 @@ public class QueryTest extends BaseCMISTest testQuery("SELECT * FROM test:extendedContent WHERE 'Un tokenised' = ANY test:singleTextBoth ", 1, false, "cmis:name", new String(), true); testQuery("SELECT * FROM test:extendedContent WHERE ANY test:singleTextBoth IN ('Un tokenised', 'Monkey')", 1, false, "cmis:name", new String(), true); testQuery("SELECT * FROM test:extendedContent WHERE ANY test:singleTextBoth NOT IN ('Un tokenized')", 1, false, "cmis:name", new String(), true); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth < 'tokenised'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth < 'Un tokenised'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth < 'V'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth < 'U'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth <= 'tokenised'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth <= 'Un tokenised'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth <= 'V'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth <= 'U'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth > 'tokenised'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth > 'Un tokenised'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth > 'V'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth > 'U'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth >= 'tokenised'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth >= 'Un tokenised'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth >= 'V'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth >= 'U'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised = 'Un tokenised'", 1, false, "cmis:name", new String(), false); testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised <> 'tokenised'", 1, false, "cmis:name", new String(), false); @@ -3833,6 +3871,22 @@ public class QueryTest extends BaseCMISTest testQuery("SELECT * FROM test:extendedContent WHERE 'Un tokenised' = ANY test:singleTextUntokenised ", 1, false, "cmis:name", new String(), true); testQuery("SELECT * FROM test:extendedContent WHERE ANY test:singleTextUntokenised IN ('Un tokenised', 'Monkey')", 1, false, "cmis:name", new String(), true); testQuery("SELECT * FROM test:extendedContent WHERE ANY test:singleTextUntokenised NOT IN ('Un tokenized')", 1, false, "cmis:name", new String(), true); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised < 'tokenised'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised < 'Un tokenised'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised < 'V'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised < 'U'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised <= 'tokenised'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised <= 'Un tokenised'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised <= 'V'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised <= 'U'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised > 'tokenised'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised > 'Un tokenised'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised > 'V'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised > 'U'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised >= 'tokenised'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised >= 'Un tokenised'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised >= 'V'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised >= 'U'", 1, false, "cmis:name", new String(), false); testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextTokenised = 'tokenised'", 1, false, "cmis:name", new String(), false); testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextTokenised <> 'tokenized'", 1, false, "cmis:name", new String(), false); @@ -3843,6 +3897,7 @@ public class QueryTest extends BaseCMISTest testQuery("SELECT * FROM test:extendedContent WHERE 'tokenised' = ANY test:singleTextTokenised ", 1, false, "cmis:name", new String(), true); testQuery("SELECT * FROM test:extendedContent WHERE ANY test:singleTextTokenised IN ('tokenised', 'Monkey')", 1, false, "cmis:name", new String(), true); testQuery("SELECT * FROM test:extendedContent WHERE ANY test:singleTextTokenised NOT IN ('tokenized')", 1, false, "cmis:name", new String(), true); + // Ranges do not make a lot of sense for tokenized fields // d:text single by alias diff --git a/source/java/org/alfresco/filesys/NFSServerBean.java b/source/java/org/alfresco/filesys/NFSServerBean.java index c5d7a24287..b169f2ff84 100644 --- a/source/java/org/alfresco/filesys/NFSServerBean.java +++ b/source/java/org/alfresco/filesys/NFSServerBean.java @@ -55,6 +55,8 @@ public class NFSServerBean extends AbstractLifecycleBean private ServerConfiguration m_filesysConfig; private NFSConfigSection m_nfsConfig; + private NfsServerNodeMonitor nodeMonitor; + // List of NFS server components private Vector m_serverList = new Vector(); @@ -79,6 +81,11 @@ public class NFSServerBean extends AbstractLifecycleBean return m_filesysConfig; } + public void setNodeMonitor(NfsServerNodeMonitor nodeMonitor) + { + this.nodeMonitor = nodeMonitor; + } + /** * Check if the server is started/enabled * @@ -113,7 +120,12 @@ public class NFSServerBean extends AbstractLifecycleBean // Create the mount and main NFS servers m_serverList.add(new MountServer(m_filesysConfig)); - m_serverList.add(new NFSServer(m_filesysConfig)); + NFSServer nfsServer = new NFSServer(m_filesysConfig); + m_serverList.add(nfsServer); + if (null != nodeMonitor) + { + nodeMonitor.setNfsServer(nfsServer); + } // Add the servers to the configuration @@ -152,6 +164,11 @@ public class NFSServerBean extends AbstractLifecycleBean */ public final void stopServer() { + if (null != nodeMonitor) + { + nodeMonitor.setEnabled(false); + } + if (m_filesysConfig == null) { // initialisation failed diff --git a/source/java/org/alfresco/filesys/NfsServerNodeMonitor.java b/source/java/org/alfresco/filesys/NfsServerNodeMonitor.java new file mode 100644 index 0000000000..52013f9b60 --- /dev/null +++ b/source/java/org/alfresco/filesys/NfsServerNodeMonitor.java @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2006-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.filesys; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.filesys.repo.ContentContext; +import org.alfresco.jlan.oncrpc.nfs.NFSServer; +import org.alfresco.jlan.oncrpc.nfs.ShareDetails; +import org.alfresco.jlan.server.core.DeviceContext; +import org.alfresco.jlan.server.filesys.FileName; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.node.NodeServicePolicies; +import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy; +import org.alfresco.repo.node.NodeServicePolicies.OnDeleteNodePolicy; +import org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.QName; +import org.apache.log4j.Logger; +import org.springframework.beans.factory.InitializingBean; + +/** + * Node monitor for NFS server which updates NFS cache on renaming or deleting nodes not through NFS protocol. This monitor may be dynamically enabled or disabled. It handles nodes + * for ${filesystem.name} device name + * + * @author Dmitry Velichkevich + */ +public class NfsServerNodeMonitor + implements + NodeServicePolicies.OnUpdatePropertiesPolicy, + NodeServicePolicies.BeforeDeleteNodePolicy, + NodeServicePolicies.OnDeleteNodePolicy, + InitializingBean +{ + private static final Logger LOGGER = Logger.getLogger(NfsServerNodeMonitor.class); + + public static final char NIX_SEPARATOR = '/'; + public static final String NIX_SEPARATOR_STR = "/"; + + // + // Not static fields (or bean properties) + // + + private Boolean enabled; + + private String targetDeviceName; + + // Calculable value + private StoreRef targetStoreRef; + + private List filesystemContexts; + + private NodeService nodeService; + private PolicyComponent policyComponent; + private PermissionService permissionService; + + /** + * Calculable value (see {@link NFSServerBean}) + */ + private NFSServer nfsServer; + + private Map cachedNodes = new HashMap(); + + public NfsServerNodeMonitor() + { + } + + /** + * Enables or disables policy handlers + * + * @param enabled {@link Boolean} value which determines working state of the handler + */ + public void setEnabled(boolean enabled) + { + Object previousState = this.enabled; + this.enabled = enabled; + if (null != previousState) + { + initialize(); + } + } + + public Boolean isEnabled() + { + return enabled; + } + + public void setTargetDeviceName(String targetDeviceName) + { + this.targetDeviceName = targetDeviceName; + } + + public String getTargetDeviceName() + { + return targetDeviceName; + } + + public void setFilesystemContexts(List filesystemContexts) + { + this.filesystemContexts = filesystemContexts; + } + + public StoreRef getTargetStoreRef() + { + return targetStoreRef; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + public void setNfsServer(NFSServer nfsServer) + { + this.nfsServer = nfsServer; + } + + @Override + public void afterPropertiesSet() throws Exception + { + initialize(); + } + + /** + * Performs all check on mandatory properties, searches for {@link StoreRef} for root path of target device and registers policy handlers for NFS cache updating on node + * properties updating and node deleting + */ + private void initialize() + { + if (enabled) + { + if (null == filesystemContexts) + { + throw new AlfrescoRuntimeException("'filesystemContexts' property is not configured"); + } + for (DeviceContext context : filesystemContexts) + { + if ((context instanceof ContentContext) && (null != context.getDeviceName()) && context.getDeviceName().equals(targetDeviceName)) + { + ContentContext targetContext = (ContentContext) context; + if (null != targetContext.getStoreName()) + { + targetStoreRef = new StoreRef(targetContext.getStoreName()); + } + break; + } + } + if (null == targetStoreRef) + { + throw new AlfrescoRuntimeException("Target Store Reference can't be found for '" + targetDeviceName + + "' device name. Check correctness of 'targetDeviceName' and 'filesystemContexts' properties configurations"); + } + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("StoreRef='" + targetStoreRef + "' was found for '" + targetDeviceName + "' device name"); + } + + policyComponent.bindClassBehaviour(OnDeleteNodePolicy.QNAME, this, new JavaBehaviour(this, "onDeleteNode")); + policyComponent.bindClassBehaviour(BeforeDeleteNodePolicy.QNAME, this, new JavaBehaviour(this, "beforeDeleteNode")); + policyComponent.bindClassBehaviour(OnUpdatePropertiesPolicy.QNAME, this, new JavaBehaviour(this, "onUpdateProperties")); + } + else + { + LOGGER.warn("NodeMonitor for NFS server is not enabled! Cache of NFS server will be never synchronized with target filesystem"); + } + } + + @Override + public void onUpdateProperties(NodeRef nodeRef, Map before, Map after) + { + if (enabled && (null != nfsServer) && targetStoreRef.equals(nodeRef.getStoreRef())) + { + int dbId = DefaultTypeConverter.INSTANCE.intValue(nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID)); + if (null == findShareDetailsForId(dbId)) + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Node with nodeRef='" + nodeRef + "' and dbId='" + dbId + "' is not in NFS server cache"); + } + + return; + } + cachedNodes.put(nodeRef, dbId); + + String oldName = DefaultTypeConverter.INSTANCE.convert(String.class, before.get(ContentModel.PROP_NAME)); + String newName = DefaultTypeConverter.INSTANCE.convert(String.class, after.get(ContentModel.PROP_NAME)); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("oldName='" + oldName + "', newName='" + newName + "'"); + } + + if (((null == oldName) && (null != newName)) || ((null != oldName) && !oldName.equals(newName))) + { + String path = buildRelativePath(nodeRef, newName); + + updateNfsCache(nodeRef, path); + } + } + } + + @Override + public void beforeDeleteNode(NodeRef nodeRef) + { + if (enabled && (null != nfsServer) && (null != nodeRef) && targetStoreRef.equals(nodeRef.getStoreRef())) + { + int dbId = DefaultTypeConverter.INSTANCE.intValue(nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID)); + cachedNodes.put(nodeRef, dbId); + } + } + + @Override + public void onDeleteNode(ChildAssociationRef childAssocRef, boolean isNodeArchived) + { + NodeRef nodeRef = (null != childAssocRef) ? (childAssocRef.getChildRef()) : (null); + if (enabled && (null != nfsServer) && (null != nodeRef) && targetStoreRef.equals(nodeRef.getStoreRef())) + { + updateNfsCache(nodeRef, null); + } + } + + /** + * Searches for {@link ShareDetails} to access NFS server cache for specific device name (e.g. 'Alfresco', 'AVM' etc) + * + * @param fileId - {@link Integer} value which contains fileId specific to device + * @return {@link ShareDetails} instance which contains fileId key in the cache or null if such instance was not found + */ + private ShareDetails findShareDetailsForId(int fileId) + { + if ((null == nfsServer) || (null == nfsServer.getShareDetails())) + { + return null; + } + + Hashtable details = nfsServer.getShareDetails().getShareDetails(); + for (Integer key : details.keySet()) + { + ShareDetails shareDetails = details.get(key); + if (null != shareDetails.getFileIdCache().findPath(fileId)) + { + return shareDetails; + } + } + + return null; + } + + /** + * Builds path relative to NFS device name e.g. for the nfs.domain.name:/DeviceName/folder/document.doc method will return \folder\document.doc + * + * @param nodeRef - {@link NodeRef} instance for target node + * @param newName - {@link String} value which contains new name for the node + * @return {@link String} value of relative path + */ + private String buildRelativePath(NodeRef nodeRef, String newName) + { + String result = null; + Path path = nodeService.getPath(nodeRef); + if (null != path) + { + StringBuilder newPath = new StringBuilder(path.toDisplayPath(nodeService, permissionService)); + if ((NIX_SEPARATOR == newPath.charAt(0)) || (FileName.DOS_SEPERATOR == newPath.charAt(0))) + { + newPath.delete(0, 1); + } + int indexOfFirstSeparator = newPath.indexOf(NIX_SEPARATOR_STR); + indexOfFirstSeparator = (-1 == indexOfFirstSeparator) ? (newPath.indexOf(FileName.DOS_SEPERATOR_STR)) : (indexOfFirstSeparator); + char lastChar = newPath.charAt(newPath.length() - 1); + if ((FileName.DOS_SEPERATOR != lastChar) && (NIX_SEPARATOR != lastChar)) + { + newPath.append(FileName.DOS_SEPERATOR); + } + if (-1 == indexOfFirstSeparator) + { + indexOfFirstSeparator = newPath.length() - 1; + } + newPath = newPath.append(newName).delete(0, indexOfFirstSeparator); + result = newPath.toString().replace(NIX_SEPARATOR, FileName.DOS_SEPERATOR); + } + return result; + } + + /** + * Updates NFS cache for specified node. newPath equal to null determines that node should be deleted from the cache + * + * @param nodeRef - {@link NodeRef} value of the target node + * @param newPath - {@link String} value or null to determine new cache value for specified node + */ + private void updateNfsCache(NodeRef nodeRef, String newPath) + { + int dbId = -1; + if (cachedNodes.containsKey(nodeRef)) + { + dbId = (null != cachedNodes.get(nodeRef)) ? (cachedNodes.get(nodeRef)) : (-1); + cachedNodes.remove(nodeRef); + } + else + { + if (nodeService.exists(nodeRef)) + { + dbId = DefaultTypeConverter.INSTANCE.intValue(nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID)); + } + } + + ShareDetails shareDetails = findShareDetailsForId(dbId); + if (null != shareDetails) + { + if (null != newPath) + { + shareDetails.getFileIdCache().addPath(dbId, newPath); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Path='" + newPath + "' in cache was set for NodeRef='" + nodeRef + "', dbId ='" + dbId + "'"); + } + } + else + { + shareDetails.getFileIdCache().deletePath(dbId); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Cache field for node with NodeRef='" + nodeRef + "', dbId='" + dbId + "' was removed"); + } + } + } + } + + @Override + public boolean equals(Object obj) + { + if (obj instanceof NfsServerNodeMonitor) + { + NfsServerNodeMonitor converted = (NfsServerNodeMonitor) obj; + return areEqual(targetDeviceName, converted.getTargetDeviceName()) && areEqual(targetStoreRef, converted.getTargetStoreRef()); + } + return false; + } + + private boolean areEqual(Object left, Object right) + { + return (null != left) ? (left.equals(right)) : (null == right); + } + + @Override + public int hashCode() + { + int result = (null != targetDeviceName) ? (targetDeviceName.hashCode()) : (31); + return result * 37 + ((null != targetStoreRef) ? (targetStoreRef.hashCode()) : (43)); + } +} diff --git a/source/java/org/alfresco/filesys/ServerConfigurationBean.java b/source/java/org/alfresco/filesys/ServerConfigurationBean.java index e48eeb9cf0..21d6962aef 100644 --- a/source/java/org/alfresco/filesys/ServerConfigurationBean.java +++ b/source/java/org/alfresco/filesys/ServerConfigurationBean.java @@ -1414,17 +1414,6 @@ public class ServerConfigurationBean extends AbstractServerConfigurationBean { ftpConfig.setFTPDebug(ftpDbg); } - // Check if IPv6 support should be enabled - - elem = config.getConfigElement("IPv6"); - if ( elem != null) { - - // Enable IPv6 support - - if ( elem.hasAttribute("state") && elem.getAttribute("state").equalsIgnoreCase("enabled")) - ftpConfig.setIPv6Enabled( true); - } - // Check if a character set has been specified elem = config.getConfigElement( "charSet"); diff --git a/source/java/org/alfresco/filesys/alfresco/AlfrescoDiskDriver.java b/source/java/org/alfresco/filesys/alfresco/AlfrescoDiskDriver.java index 2965e45f52..5376bfbcd3 100644 --- a/source/java/org/alfresco/filesys/alfresco/AlfrescoDiskDriver.java +++ b/source/java/org/alfresco/filesys/alfresco/AlfrescoDiskDriver.java @@ -158,6 +158,9 @@ public abstract class AlfrescoDiskDriver implements IOCtlInterface, Transactiona /** * Perform a retryable operation in a write transaction + *

+ * WARNING : side effect - that the current transaction, if any, is ended. + * * * @param sess * the server session diff --git a/source/java/org/alfresco/filesys/alfresco/DesktopAction.java b/source/java/org/alfresco/filesys/alfresco/DesktopAction.java index bdaf963acd..78bea11196 100644 --- a/source/java/org/alfresco/filesys/alfresco/DesktopAction.java +++ b/source/java/org/alfresco/filesys/alfresco/DesktopAction.java @@ -19,15 +19,15 @@ package org.alfresco.filesys.alfresco; -import java.io.File; import java.io.IOException; import org.alfresco.jlan.server.filesys.DiskSharedDevice; -import org.alfresco.jlan.server.filesys.pseudo.LocalPseudoFile; +import org.alfresco.jlan.server.filesys.pseudo.MemoryPseudoFile; import org.alfresco.jlan.server.filesys.pseudo.PseudoFile; import org.alfresco.repo.admin.SysAdminParams; import org.alfresco.service.ServiceRegistry; import org.alfresco.util.ResourceFinder; +import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.io.Resource; @@ -108,7 +108,7 @@ public abstract class DesktopAction { // Filesystem driver and context - private AlfrescoDiskDriver m_filesysDriver; + protected AlfrescoDiskDriver m_filesysDriver; private AlfrescoContext m_filesysContext; // Webapp URL @@ -400,35 +400,23 @@ public abstract class DesktopAction { throw new DesktopActionException("Desktop action executable path not specified"); // Check that the application exists on the local filesystem + Resource resource = new ResourceFinder().getResource(m_path); if (!resource.exists()) { throw new DesktopActionException("Failed to find drag and drop application, " + m_path); } - // Decode the URL path, it might contain escaped characters - - - // Check that the drag/drop file exists - File appFile; + PseudoFile pseudoFile = null; try { - appFile = resource.getFile(); - - if (!appFile.exists()) - { - throw new DesktopActionException("Drag and drop application not found, " + appFile); - } + pseudoFile = new MemoryPseudoFile(m_filename, IOUtils.toByteArray(resource.getInputStream())); } catch (IOException e) { - throw new DesktopActionException("Unable to resolve drag and drop application as a file, " - + resource.getDescription()); + throw new DesktopActionException("Drag and drop application resource is invalid, " + resource.getDescription()); } - // Create the pseudo file for the action - - PseudoFile pseudoFile = new LocalPseudoFile(m_filename, appFile.getAbsolutePath()); setPseudoFile(pseudoFile); } diff --git a/source/java/org/alfresco/filesys/alfresco/DesktopActionException.java b/source/java/org/alfresco/filesys/alfresco/DesktopActionException.java index 195c1b655d..d49367f173 100644 --- a/source/java/org/alfresco/filesys/alfresco/DesktopActionException.java +++ b/source/java/org/alfresco/filesys/alfresco/DesktopActionException.java @@ -34,8 +34,8 @@ public class DesktopActionException extends Exception { /** * Class constructor * - * @param sts int - * @param msg String + * @param sts numeric status code. + * @param msg readable error message */ public DesktopActionException(int sts, String msg) { diff --git a/source/java/org/alfresco/filesys/alfresco/MultiTenantShareMapper.java b/source/java/org/alfresco/filesys/alfresco/MultiTenantShareMapper.java index 3dbc436866..53a2289b0f 100644 --- a/source/java/org/alfresco/filesys/alfresco/MultiTenantShareMapper.java +++ b/source/java/org/alfresco/filesys/alfresco/MultiTenantShareMapper.java @@ -33,6 +33,7 @@ import org.alfresco.jlan.server.config.ConfigurationListener; import org.alfresco.jlan.server.config.InvalidConfigurationException; import org.alfresco.jlan.server.config.ServerConfiguration; import org.alfresco.jlan.server.core.ShareMapper; +import org.alfresco.jlan.server.core.ShareType; import org.alfresco.jlan.server.core.SharedDevice; import org.alfresco.jlan.server.core.SharedDeviceList; import org.alfresco.jlan.server.filesys.DiskSharedDevice; @@ -195,7 +196,8 @@ public class MultiTenantShareMapper implements ShareMapper, ConfigurationListene // Check if this is a tenant user - if ( m_alfrescoConfig.getTenantService().isEnabled() && m_alfrescoConfig.getTenantService().isTenantUser()) + if ( m_alfrescoConfig.getTenantService().isEnabled() && m_alfrescoConfig.getTenantService().isTenantUser() && + typ != ShareType.ADMINPIPE) return findTenantShare(host, name, typ, sess, create); // Find the required share by name/type. Use a case sensitive search first, if that fails use a case diff --git a/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java b/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java index 2ee6307686..362246174f 100644 --- a/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java +++ b/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java @@ -807,9 +807,16 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement sess.removeSetupObject( client.getProcessId()); - // Convert to an access denied exception + // Convert to an access denied exception if necessary - throw new SMBSrvException( SMBStatus.NTAccessDenied, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); + if (ex instanceof AlfrescoRuntimeException && ex.getCause() instanceof SMBSrvException) + { + throw (SMBSrvException) ex.getCause(); + } + else + { + throw new SMBSrvException( SMBStatus.NTAccessDenied, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); + } } // Debug diff --git a/source/java/org/alfresco/filesys/config/FTPConfigBean.java b/source/java/org/alfresco/filesys/config/FTPConfigBean.java index f09ff9f0c3..ae829b48ff 100644 --- a/source/java/org/alfresco/filesys/config/FTPConfigBean.java +++ b/source/java/org/alfresco/filesys/config/FTPConfigBean.java @@ -56,9 +56,6 @@ public class FTPConfigBean /** The authenticator. */ private FTPAuthenticator authenticator; - /** Is IP v6 enabled? */ - private boolean ipv6Enabled; - // Data port range private int dataPortFrom; @@ -270,27 +267,6 @@ public class FTPConfigBean this.authenticator = authenticator; } - /** - * Checks if IP v6 is enabled. - * - * @return true if IP v6 is enabled - */ - public boolean getIpv6Enabled() - { - return ipv6Enabled; - } - - /** - * Indicates whether IP v6 should be enabled. - * - * @param ipv6Enabled - * true if IP v6 should be enabled - */ - public void setIpv6Enabled(boolean ipv6Enabled) - { - this.ipv6Enabled = ipv6Enabled; - } - /** * Return the data port range from port * diff --git a/source/java/org/alfresco/filesys/config/ServerConfigurationBean.java b/source/java/org/alfresco/filesys/config/ServerConfigurationBean.java index 01ddc49307..385850f565 100644 --- a/source/java/org/alfresco/filesys/config/ServerConfigurationBean.java +++ b/source/java/org/alfresco/filesys/config/ServerConfigurationBean.java @@ -1256,10 +1256,6 @@ public class ServerConfigurationBean extends AbstractServerConfigurationBean ftpConfig.setFTPDebug(ftpDbg); } - // Check if IPv6 support should be enabled - - ftpConfig.setIPv6Enabled(ftpConfigBean.getIpv6Enabled()); - // Check if a character set has been specified String charSet = ftpConfigBean.getCharSet(); diff --git a/source/java/org/alfresco/filesys/repo/CifsHelper.java b/source/java/org/alfresco/filesys/repo/CifsHelper.java index e5c5dc38a7..d6b1612083 100644 --- a/source/java/org/alfresco/filesys/repo/CifsHelper.java +++ b/source/java/org/alfresco/filesys/repo/CifsHelper.java @@ -22,8 +22,10 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.Stack; import java.util.StringTokenizer; @@ -68,11 +70,14 @@ public class CifsHelper private MimetypeService mimetypeService; private PermissionService permissionService; private boolean isReadOnly; + private boolean setReadOnlyFlagOnFolders; // Mark locked files as offline private boolean lockedFilesAsOffline; + private Set excludedTypes = new HashSet(); + /** * Class constructor */ @@ -115,6 +120,14 @@ public class CifsHelper return nodeService; } + public void setExcludedTypes(List excludedTypes) + { + for(String exType:excludedTypes) + { + this.excludedTypes.add(QName.createQName(exType)); + } + } + /** * @return Returns true if all files/folders should be treated as read-only */ @@ -153,6 +166,19 @@ public class CifsHelper return lockedFilesAsOffline; } + /** + * Controls whether the read only flag is set on folders. This flag, when set, may cause problematic # behaviour in + * Windows clients and doesn't necessarily mean a folder can't be written to. See ALF-6727. Should we ever set the + * read only flag on folders? + * + * @param setReadOnlyFlagOnFolders + * the setReadOnlyFlagOnFolders to set + */ + public void setSetReadOnlyFlagOnFolders(boolean setReadOnlyFlagOnFolders) + { + this.setReadOnlyFlagOnFolders = setReadOnlyFlagOnFolders; + } + /** * @param serviceRegistry for repo connection * @param nodeRef @@ -292,14 +318,17 @@ public class CifsHelper // Read/write access - boolean deniedPermission = permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED; - if (isReadOnly || deniedPermission) + if (!fileFolderInfo.isFolder() || setReadOnlyFlagOnFolders) { - int attr = fileInfo.getFileAttributes(); - if (( attr & FileAttribute.ReadOnly) == 0) + boolean deniedPermission = permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED; + if (isReadOnly || deniedPermission) { - attr += FileAttribute.ReadOnly; - fileInfo.setFileAttributes(attr); + int attr = fileInfo.getFileAttributes(); + if (( attr & FileAttribute.ReadOnly) == 0) + { + attr += FileAttribute.ReadOnly; + fileInfo.setFileAttributes(attr); + } } } @@ -498,9 +527,19 @@ public class CifsHelper // result storage List results = new ArrayList(5); + List rubeResults = new ArrayList(5); // kick off the path walking - addDescendents(pathRootNodeRefs, pathElements, results); + addDescendents(pathRootNodeRefs, pathElements, rubeResults); + + for (NodeRef nodeRef : rubeResults) + { + QName nodeType = nodeService.getType(nodeRef); + if (!excludedTypes.contains(nodeType)) + { + results.add(nodeRef); + } + } // done if (logger.isDebugEnabled()) diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java index e36093fae0..24242d360e 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java @@ -80,11 +80,15 @@ import org.alfresco.jlan.util.MemorySize; import org.alfresco.jlan.util.WildCard; import org.alfresco.model.ContentModel; import org.alfresco.repo.admin.SysAdminParams; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.encoding.ContentCharsetFinder; import org.alfresco.repo.node.archive.NodeArchiveService; import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.security.authentication.AuthenticationContext; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.lock.LockService; import org.alfresco.service.cmr.lock.LockType; @@ -102,6 +106,7 @@ import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.AccessPermission; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.OwnableService; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; @@ -120,7 +125,6 @@ import org.springframework.extensions.config.ConfigElement; public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterface, FileLockingInterface, OpLockInterface, DiskSizeInterface { // Logging - private static final Log logger = LogFactory.getLog(ContentDiskDriver.class); // Configuration key names @@ -143,6 +147,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // File state attributes public static final String AttrLinkNode = "ContentLinkNode"; + public static final String CanDeleteWithoutPerms = "CanDeleteWithoutPerms"; // List of content properties to copy during rename @@ -179,6 +184,8 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa private NodeArchiveService nodeArchiveService; private LockService lockService; private DictionaryService dictionaryService; + private OwnableService ownableService; + private ActionService actionService; private AuthenticationContext authContext; private AuthenticationService authService; @@ -319,6 +326,15 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa return dictionaryService; } + /** + * Get the ownable service + * + * @return OwnableService + */ + public final OwnableService getOwnableService() { + return ownableService; + } + /** * @param contentService the content service */ @@ -456,25 +472,35 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa this.dictionaryService = dictionaryService; } + /** + * Set the ownable servive + * + * @param ownableService OwnableService + */ + public void setOwnableService(OwnableService ownableService) { + this.ownableService = ownableService; + } + /** * Parse and validate the parameter string and create a device context object for this instance * of the shared device. The same DeviceInterface implementation may be used for multiple * shares. + *

+ * WARNING: side effect, may commit or roll back current user transaction context. * - * @param shareName String - * @param args ConfigElement + * @param deviceName The name of the device + * @param cfg ConfigElement the configuration of the device context. * @return DeviceContext * @exception DeviceContextException */ - public DeviceContext createContext(String shareName, ConfigElement cfg) throws DeviceContextException + // MER TODO - transaction handling in registerContext needs changing + public DeviceContext createContext(String deviceName, ConfigElement cfg) throws DeviceContextException { ContentContext context = null; try { - // Get the store - ConfigElement storeElement = cfg.getChild(KEY_STORE); if (storeElement == null || storeElement.getValue() == null || storeElement.getValue().length() == 0) { @@ -495,7 +521,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Create the context context = new ContentContext(); - context.setDeviceName(shareName); + context.setDeviceName(deviceName); context.setStoreName(storeValue); context.setRootPath(rootPath); context.setSysAdminParams(this.sysAdminParams); @@ -511,14 +537,18 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa String relPath = relativePathElement.getValue().replace( '/', FileName.DOS_SEPERATOR); context.setRelativePath(relPath); } - - } - catch (Exception ex) + /* + * MER - I changed the code below - resulted in a NPE anyway + * lower down + */ + catch (DeviceContextException ex) { logger.error("Error during create context", ex); + throw ex; + } - + // Check if URL link files are enabled ConfigElement urlFileElem = cfg.getChild( "urlFile"); @@ -544,6 +574,8 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Install the node service monitor + // MER 01/03/2011 - I think one of these is the "wrong way round" + if ( cfg.getChild("disableNodeMonitor") == null) { // Create the node monitor @@ -571,9 +603,12 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa * of the shared device. The same DeviceInterface implementation may be used for multiple * shares. * + * WARNING: side effect, will commit or roll back current user transaction context. + * * @param ctx the context * @exception DeviceContextException */ + // MER TODO - transaction handling in registerContext needs changing @Override public void registerContext(DeviceContext ctx) throws DeviceContextException { @@ -664,6 +699,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Commit the transaction + // MER 16/03/2010 - Why is this transaction management here? tx.commit(); tx = null; @@ -673,6 +709,10 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa catch (Exception ex) { logger.error("Error during create context", ex); + + // MER BUGBUG Exception swallowed - will result in null pointer errors at best. + throw new DeviceContextException("unable to register context", ex); + // MER END } finally { @@ -1333,10 +1373,13 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa /** * Check if the specified file exists, and whether it is a file or directory. * + *

+ * WARNING: side effect, commit or roll back current user transaction context. Current transaction becomes read only. + * * @param sess Server session * @param tree Tree connection - * @param name java.lang.String - * @return int + * @param name the path of the file + * @return FileStatus (0: NotExist, 1 : FileExist, 2: DirectoryExists) * @see FileStatus */ public int fileExists(SrvSession sess, TreeConnection tree, String name) @@ -1655,78 +1698,78 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa logger.debug( " Fstate=" + fstate); } - throw new AccessDeniedException("Invalid access mode"); - } - - if ( fstate.getOpenCount() > 0) { - - // Check for impersonation security level from the original process that opened the file - - if ( params.getSecurityLevel() == WinNT.SecurityImpersonation && params.getProcessId() == fstate.getProcessId()) - nosharing = false; - - // Check if the caller wants read access, check the sharing mode - - else if ( params.isReadOnlyAccess() && (fstate.getSharedAccess() & SharingMode.READ) != 0) - nosharing = false; - - // Check if the caller wants write access, check the sharing mode - - else if (( params.isReadWriteAccess() || params.isWriteOnlyAccess()) && (fstate.getSharedAccess() & SharingMode.WRITE) == 0) - { - // DEBUG - - if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) - logger.debug("Sharing mode disallows write access path=" + params.getPath()); - - // Access not allowed - - throw new AccessDeniedException( "Sharing mode (write)"); - } - - // Check if the file has been opened for exclusive access - - else if ( fstate.getSharedAccess() == SharingMode.NOSHARING) - nosharing = true; - - // Check if the required sharing mode is allowed by the current file open - - else if ( ( fstate.getSharedAccess() & params.getSharedAccess()) != params.getSharedAccess()) - nosharing = true; - - // Check if the caller wants exclusive access to the file - - else if ( params.getSharedAccess() == SharingMode.NOSHARING) - nosharing = true; - - } - - // Check if the file allows shared access - - if ( nosharing == true) - { - if ( params.getPath().equals( "\\") == false) { - - // DEBUG - - if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) - logger.debug("Sharing violation path=" + params.getPath() + ", sharing=0x" + Integer.toHexString(fstate.getSharedAccess())); - - // File is locked by another user - - throw new FileSharingException("File already open, " + params.getPath()); - } + throw new AccessDeniedException("Invalid access mode"); } - - // Update the file sharing mode and process id, if this is the first file open - - fstate.setSharedAccess( params.getSharedAccess()); - fstate.setProcessId( params.getProcessId()); - - // DEBUG - - if ( logger.isDebugEnabled() && fstate.getOpenCount() == 0 && ctx.hasDebug(AlfrescoContext.DBG_FILE)) - logger.debug("Path " + params.getPath() + ", sharing=0x" + Integer.toHexString(params.getSharedAccess()) + ", PID=" + params.getProcessId()); + + if ( fstate.getOpenCount() > 0) { + + // Check for impersonation security level from the original process that opened the file + + if ( params.getSecurityLevel() == WinNT.SecurityImpersonation && params.getProcessId() == fstate.getProcessId()) + nosharing = false; + + // Check if the caller wants read access, check the sharing mode + + else if ( params.isReadOnlyAccess() && (fstate.getSharedAccess() & SharingMode.READ) != 0) + nosharing = false; + + // Check if the caller wants write access, check the sharing mode + + else if (( params.isReadWriteAccess() || params.isWriteOnlyAccess()) && (fstate.getSharedAccess() & SharingMode.WRITE) == 0) + { + // DEBUG + + if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) + logger.debug("Sharing mode disallows write access path=" + params.getPath()); + + // Access not allowed + + throw new AccessDeniedException( "Sharing mode (write)"); + } + + // Check if the file has been opened for exclusive access + + else if ( fstate.getSharedAccess() == SharingMode.NOSHARING) + nosharing = true; + + // Check if the required sharing mode is allowed by the current file open + + else if ( ( fstate.getSharedAccess() & params.getSharedAccess()) != params.getSharedAccess()) + nosharing = true; + + // Check if the caller wants exclusive access to the file + + else if ( params.getSharedAccess() == SharingMode.NOSHARING) + nosharing = true; + + } + + // Check if the file allows shared access + + if ( nosharing == true) + { + if ( params.getPath().equals( "\\") == false) { + + // DEBUG + + if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) + logger.debug("Sharing violation path=" + params.getPath() + ", sharing=0x" + Integer.toHexString(fstate.getSharedAccess())); + + // File is locked by another user + + throw new FileSharingException("File already open, " + params.getPath()); + } + } + + // Update the file sharing mode and process id, if this is the first file open + + fstate.setSharedAccess( params.getSharedAccess()); + fstate.setProcessId( params.getProcessId()); + + // DEBUG + + if ( logger.isDebugEnabled() && fstate.getOpenCount() == 0 && ctx.hasDebug(AlfrescoContext.DBG_FILE)) + logger.debug("Path " + params.getPath() + ", sharing=0x" + Integer.toHexString(params.getSharedAccess()) + ", PID=" + params.getProcessId()); } // Check if the node is a link node @@ -1736,68 +1779,68 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa if ( linkRef == null) { - // Check if the file is already opened by this client/process - - if ( tree.openFileCount() > 0) { - - // Search the open file table for this session/virtual circuit - - int idx = 0; - - while ( idx < tree.getFileTableLength() && netFile == null) { - - // Get the current file from the open file table - - NetworkFile curFile = tree.findFile( idx); - if ( curFile != null && curFile instanceof ContentNetworkFile) { - - // Check if the file is the same path and process id - - ContentNetworkFile contentFile = (ContentNetworkFile) curFile; - if ( contentFile.getProcessId() == params.getProcessId() && - contentFile.getFullName().equalsIgnoreCase( params.getFullPath())) { - - // Check that the access mode is the same - - if (( params.isReadWriteAccess() && contentFile.getGrantedAccess() == NetworkFile.READWRITE) || - ( params.isReadOnlyAccess() && contentFile.getGrantedAccess() == NetworkFile.READONLY)) { - - // Found a match, re-use the open file - - netFile = contentFile; - - // Increment the file open count, last file close will actually close the file/stream - - contentFile.incrementOpenCount(); - - // DEBUG - - if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) - logger.debug("Re-use existing file open Path " + params.getPath() + ", PID=" + params.getProcessId() + ", params=" + - ( params.isReadOnlyAccess() ? "ReadOnly" : "Write") + ", file=" + - ( contentFile.getGrantedAccess() == NetworkFile.READONLY ? "ReadOnly" : "Write")); - } - else if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) - logger.debug("Not re-using file path=" + params.getPath() + ", readWrite=" + (params.isReadWriteAccess() ? "true" : "false") + - ", readOnly=" + (params.isReadOnlyAccess() ? "true" : "false") + - ", grantedAccess=" + contentFile.getGrantedAccessAsString()); - } - } - - // Update the file table index - - idx++; - } - } - - // Create the network file, if we could not match an existing file open - - if ( netFile == null) { - - // Create a new network file for the open request - - netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, cifsHelper, nodeRef, params, sess); - } + // Check if the file is already opened by this client/process + + if ( tree.openFileCount() > 0) { + + // Search the open file table for this session/virtual circuit + + int idx = 0; + + while ( idx < tree.getFileTableLength() && netFile == null) { + + // Get the current file from the open file table + + NetworkFile curFile = tree.findFile( idx); + if ( curFile != null && curFile instanceof ContentNetworkFile) { + + // Check if the file is the same path and process id + + ContentNetworkFile contentFile = (ContentNetworkFile) curFile; + if ( contentFile.getProcessId() == params.getProcessId() && + contentFile.getFullName().equalsIgnoreCase( params.getFullPath())) { + + // Check that the access mode is the same + + if (( params.isReadWriteAccess() && contentFile.getGrantedAccess() == NetworkFile.READWRITE) || + ( params.isReadOnlyAccess() && contentFile.getGrantedAccess() == NetworkFile.READONLY)) { + + // Found a match, re-use the open file + + netFile = contentFile; + + // Increment the file open count, last file close will actually close the file/stream + + contentFile.incrementOpenCount(); + + // DEBUG + + if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) + logger.debug("Re-use existing file open Path " + params.getPath() + ", PID=" + params.getProcessId() + ", params=" + + ( params.isReadOnlyAccess() ? "ReadOnly" : "Write") + ", file=" + + ( contentFile.getGrantedAccess() == NetworkFile.READONLY ? "ReadOnly" : "Write")); + } + else if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) + logger.debug("Not re-using file path=" + params.getPath() + ", readWrite=" + (params.isReadWriteAccess() ? "true" : "false") + + ", readOnly=" + (params.isReadOnlyAccess() ? "true" : "false") + + ", grantedAccess=" + contentFile.getGrantedAccessAsString()); + } + } + + // Update the file table index + + idx++; + } + } + + // Create the network file, if we could not match an existing file open + + if ( netFile == null) { + + // Create a new network file for the open request + + netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, cifsHelper, nodeRef, params, sess); + } } else { @@ -1819,11 +1862,11 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa srvName = InetAddress.getLocalHost().getHostName(); } - // Convert the target node to a path, convert to URL format - - String path = getPathForNode( tree, linkRef); - path = path.replace( FileName.DOS_SEPERATOR, '/'); - + // Convert the target node to a path, convert to URL format + + String path = getPathForNode( tree, linkRef); + path = path.replace( FileName.DOS_SEPERATOR, '/'); + // Build the URL file data StringBuilder urlStr = new StringBuilder(); @@ -1890,7 +1933,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Set the file access date/time, if available if ( fstate.hasAccessDateTime()) - netFile.setAccessDate( fstate.getAccessDateTime()); + netFile.setAccessDate( fstate.getAccessDateTime()); } // Debug @@ -1929,6 +1972,9 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa /** * Create a new file on the file system. * + *

+ * WARNING : side effect - closes current transaction context. + * * @param sess Server session * @param tree Tree connection * @param params File create parameters @@ -1983,6 +2029,10 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa } // Create it - the path will be created, if necessary + if(logger.isDebugEnabled()) + { + logger.debug("create new file" + path); + } NodeRef nodeRef = cifsHelper.createNode(deviceRootNodeRef, path, ContentModel.TYPE_CONTENT); nodeService.addAspect(nodeRef, ContentModel.ASPECT_NO_CONTENT, null); @@ -2058,7 +2108,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Update the parent folder file state if ( parentState != null) - parentState.updateModifyDateTime(); + parentState.updateModifyDateTime(); } // Debug @@ -2101,14 +2151,17 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Convert to a general I/O exception - throw new IOException("Create file " + params.getFullPath()); + throw new IOException("Create file " + params.getFullPath(), ex); } } /** * Create a new directory on this file system. - * + * + *

+ * WARNING : side effect - closes current transaction context. + * * @param sess Server session * @param tree Tree connection. * @param params Directory create parameters @@ -2201,7 +2254,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Update the parent folder file state if ( parentState != null) - parentState.updateModifyDateTime(); + parentState.updateModifyDateTime(); } // Debug @@ -2255,16 +2308,16 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa public NodeRef call() throws IOException { // Get the node for the folder - + NodeRef nodeRef = cifsHelper.getNodeRef(deviceRootNodeRef, dir); if (fileFolderService.exists(nodeRef)) { // Check if the folder is empty - + if ( cifsHelper.isFolderEmpty( nodeRef)) { // Delete the folder node - + fileFolderService.delete(nodeRef); return nodeRef; } @@ -2277,7 +2330,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa }}); if (nodeRef != null && ctx.hasStateCache()) { - // Remove the file state + // Remove the file state ctx.getStateCache().removeFileState(dir); @@ -2289,8 +2342,8 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Get the file state for the parent folder FileState parentState = getStateForPath(tree, paths[0]); - if ( parentState == null && ctx.hasStateCache()) - parentState = ctx.getStateCache().findFileState( paths[0], true); + if ( parentState == null && ctx.hasStateCache()) + parentState = ctx.getStateCache().findFileState( paths[0], true); // Update the modification timestamp @@ -2306,7 +2359,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa catch (FileNotFoundException e) { // Debug - + if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) logger.debug("Delete directory - file not found, " + dir); } @@ -2344,13 +2397,13 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa */ public void flushFile(SrvSession sess, TreeConnection tree, NetworkFile file) throws IOException { - // Debug - - ContentContext ctx = (ContentContext) tree.getContext(); - - if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILEIO)) - logger.debug("Flush file=" + file.getFullName()); - + // Debug + + ContentContext ctx = (ContentContext) tree.getContext(); + + if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILEIO)) + logger.debug("Flush file=" + file.getFullName()); + // Flush the file data file.flushFile(); @@ -2365,7 +2418,12 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa * @exception java.io.IOException If an error occurs. */ public void closeFile(SrvSession sess, TreeConnection tree, final NetworkFile file) throws IOException - { + { + if (logger.isDebugEnabled()) + { + logger.debug("Close file: file" + file); + } + // Get the associated file state final ContentContext ctx = (ContentContext) tree.getContext(); @@ -2374,15 +2432,15 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Check for a content file if ( file instanceof ContentNetworkFile) { - + // Update the file state if ( ctx.hasStateCache()) { FileState fstate = ctx.getStateCache().findFileState(file.getFullName()); if ( fstate != null) { - - // If the file open count is now zero then reset the stored sharing mode + + // If the file open count is now zero then reset the stored sharing mode if ( fstate.decrementOpenCount() == 0) fstate.setSharedAccess( SharingMode.READWRITE + SharingMode.DELETE); @@ -2390,7 +2448,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Check if there is a cached modification timestamp to be written out if ( file.hasDeleteOnClose() == false && fstate.hasModifyDateTime() && fstate.hasFilesystemObject() && fstate.isDirectory() == false) { - + // Update the modification date on the file/folder node toUpdate = fstate; } @@ -2446,6 +2504,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa NodeRef nodeRef = ((NodeRefNetworkFile) file).getNodeRef(); if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_NO_CONTENT)) { + logger.debug("No content - delete"); fileFolderService.delete(nodeRef); } } @@ -2519,18 +2578,19 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Defer to the network file to close the stream and remove the content - file.closeFile(); + file.close(); // Remove the node if marked for delete if (file.hasDeleteOnClose()) { + logger.debug("File has delete on close"); // Check if the file is a noderef based file if ( file instanceof NodeRefNetworkFile) { NodeRefNetworkFile nodeNetFile = (NodeRefNetworkFile) file; - NodeRef nodeRef = nodeNetFile.getNodeRef(); + final NodeRef nodeRef = nodeNetFile.getNodeRef(); // We don't know how long the network file has had the reference, so check for existence @@ -2543,12 +2603,32 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa try { // Delete the file + FileState fileState = ctx.getStateCache().findFileState(file.getFullName()); + if (fileState!= null && fileState.findAttribute(CanDeleteWithoutPerms) != null) + { + //Has CanDeleteWithoutPerms attribute, so delete from system user + AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + @Override + public Object doWork() throws Exception + { + logger.debug("delete as system" + nodeRef); + fileFolderService.delete(nodeRef); + return null; + } + + }, AuthenticationUtil.getSystemUserName()); - fileFolderService.delete(nodeRef); - + } + else + { + logger.debug("delete nodeRef:" + nodeRef); + fileFolderService.delete(nodeRef); + } } catch ( Exception ex) { + logger.debug("on delete on close", ex); // Propagate retryable errors. Log the rest. if (RetryingTransactionHelper.extractRetryCause(ex) != null) { @@ -2693,6 +2773,11 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa { // Get the device context + if ( logger.isDebugEnabled()) + { + logger.debug("Delete file - " + name); + } + final ContentContext ctx = (ContentContext) tree.getContext(); try @@ -2749,6 +2834,10 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa final boolean isVersionable = nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE); + if(logger.isDebugEnabled()) + { + logger.debug("deleted file" + name); + } fileFolderService.delete(nodeRef); // Return the operations to perform when the transaction succeeds @@ -2773,6 +2862,10 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa FileState delState = ctx.getStateCache().findFileState(name, true); + if(logger.isDebugEnabled()) + { + logger.debug("set delete on close" + name); + } delState.setExpiryTime(System.currentTimeMillis() + FileState.DeleteTimeout); delState.setFileStatus(DeleteOnClose); delState.setFilesystemObject(nodeRef); @@ -2946,7 +3039,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa { // Update the file state index to use the new name - ctx.getStateCache().renameFileState(newName, oldState, true); + ctx.getStateCache().renameFileState(newName, oldState, isFolder); } // DEBUG @@ -2955,7 +3048,8 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa logger.debug(" Renamed " + (isFolder ? "folder" : "file") + " using " + (sameFolder ? "rename" : "move")); } - else { + else + { // Rename a file within the same folder // @@ -2980,12 +3074,8 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // This code will move into the repo layer (or just above it) // and this complexity removed from here. // Attempt to detect normal renames. Hack alert! - String oldNameLower = oldName.toLowerCase(); - String newNameLower = newName.toLowerCase(); - boolean renameShuffle = false; Pattern renameShufflePattern = ctx.getRenameShufflePattern(); - renameShuffle = renameShuffle || renameShufflePattern.matcher(oldNameLower).matches(); - renameShuffle = renameShuffle || renameShufflePattern.matcher(newNameLower).matches(); + boolean renameShuffle = isRenameShuffle(oldName, newName, renameShufflePattern); if (logger.isDebugEnabled()) { logger.debug( @@ -3010,10 +3100,13 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa } else if (renameShuffle) { - + logger.debug("is rename shuffle"); // Check if the target has a renamed or delete-on-close state - if ( newState.getFileStatus() == FileRenamed) { + if ( newState.getFileStatus() == FileRenamed) + { + logger.debug("file status == FileRenamed"); + // DEBUG @@ -3026,11 +3119,12 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa if (oldType.equals(newType)) { // Use the renamed node to clone aspects/state if it is of the correct type - + cloneNode(name, newStateNode, nodeToMoveRef, ctx); } else { + logger.debug("not renamed, must create new node"); // Otherwise we must create a node of the correct type targetNodeRef = cifsHelper.createNode(ctx.getRootNode(), newName, newType); @@ -3040,14 +3134,25 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // DEBUG if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME)) - logger.debug(" Created new node for " + newName + " type " + newType); + logger.debug(" Created new node for " + newName + " type " + newType + ", isFromVersionable=false"); // Copy aspects from the original state cloneNode( name, newStateNode, targetNodeRef, ctx); } + + //Change state for tmp node to allow delete it without special permission + String newStateNodeName = (String) nodeService.getProperty(newStateNode, ContentModel.PROP_NAME); + FileState stateForTmp = ctx.getStateCache().findFileState(newName.substring(0,newName.lastIndexOf("\\")) +"\\"+ newStateNodeName); + stateForTmp.addAttribute(CanDeleteWithoutPerms, true); + stateForTmp.setExpiryTime(System.currentTimeMillis() + FileState.DeleteTimeout); + if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME)) + logger.debug(" Set CanDeleteWithoutPerms=true for " + stateForTmp); + } - else if ( newState.getFileStatus() == DeleteOnClose) { + else if ( newState.getFileStatus() == DeleteOnClose) + { + logger.debug("file state is delete on close"); // DEBUG @@ -3108,7 +3213,9 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Check if the node being renamed is versionable - else if ( isFromVersionable == true) { + else if ( isFromVersionable == true) + { + logger.debug("from node is versionable"); // Create a new node for the target @@ -3117,11 +3224,18 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // DEBUG if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME)) - logger.debug(" Created new node for " + newName); + logger.debug(" Created new node for " + newName + ", isFromVersionable=true"); // Copy aspects from the original file cloneNode( name, nodeToMoveRef, targetNodeRef, ctx); + + //Change state for tmp node to allow delete it without special permission + FileState stateForTmp = ctx.getStateCache().findFileState(newName); + stateForTmp.addAttribute(CanDeleteWithoutPerms, true); + stateForTmp.setExpiryTime(System.currentTimeMillis() + FileState.DeleteTimeout); + if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME)) + logger.debug(" Set CanDeleteWithoutPerms=true for " + stateForTmp); } } @@ -3131,6 +3245,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa typesCompatible && ( targetNodeRef == null || nodeService.hasAspect( targetNodeRef, ContentModel.ASPECT_VERSIONABLE) == false))) { + logger.debug("do simple rename"); // Rename the file/folder @@ -3162,7 +3277,8 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME)) logger.debug(" Use standard rename for " + name + "(versionable=" + isFromVersionable + ", targetNodeRef=" + targetNodeRef + ")"); } - else { + else + { // Make sure we have a valid target node @@ -3213,10 +3329,35 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa logger.debug(" Cached delete state for " + oldName); } }); + + logger.debug("delete the old file"); // Delete the old file + if (renameShuffle && isFromVersionable && permissionService.hasPermission(nodeToMoveRef, PermissionService.EDITOR) == AccessStatus.ALLOWED) + { + AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + @Override + public Object doWork() throws Exception + { + if (logger.isDebugEnabled()) + { + logger.debug("Rename shuffle for versioning content is assumed. Deleting " + nodeToMoveRef + " as system user"); + } + nodeService.deleteNode(nodeToMoveRef); + return null; + } - nodeService.deleteNode(nodeToMoveRef); + }, AuthenticationUtil.getSystemUserName()); + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("Deleting " + nodeToMoveRef + " as user: " + AuthenticationUtil.getFullyAuthenticatedUser()); + } + nodeService.deleteNode(nodeToMoveRef); + } } @@ -3224,6 +3365,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa } }); + logger.debug("running post txns"); // Run the required state-changing logic once the retrying transaction has completed successfully for (Runnable runnable : postTxn) { @@ -3275,7 +3417,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa * @param info FileInfo * @exception IOException */ - public void setFileInformation(SrvSession sess, final TreeConnection tree, final String name, final FileInfo info) throws IOException + public void setFileInformation(SrvSession sess, final TreeConnection tree, final String name , final FileInfo info) throws IOException { // Get the device context @@ -3306,10 +3448,10 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Check permissions on the file/folder node if ( permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED) - throw new AccessDeniedException("No write access to " + name); + throw new org.alfresco.repo.security.permissions.AccessDeniedException("No write access to " + name); - if ( permissionService.hasPermission(nodeRef, PermissionService.DELETE) == AccessStatus.DENIED) - throw new AccessDeniedException("No delete access to " + name); + if ( permissionService.hasPermission(nodeRef, PermissionService.DELETE) == AccessStatus.DENIED && fstate.findAttribute(CanDeleteWithoutPerms) == null) + throw new org.alfresco.repo.security.permissions.AccessDeniedException("No delete access to " + name); // Inhibit versioning for this transaction @@ -3328,7 +3470,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa String lockTypeStr = (String) nodeService.getProperty( nodeRef, ContentModel.PROP_LOCK_TYPE); if ( lockTypeStr != null) - throw new AccessDeniedException("Node locked, cannot mark for delete"); + throw new org.alfresco.repo.security.permissions.AccessDeniedException("Node locked, cannot mark for delete"); } // Get the node for the folder @@ -3404,10 +3546,10 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa if (info.hasSetFlag(FileInfo.SetModifyDate)) { - // Update the change date/time, clear the cached modification date/time fstate.updateChangeDateTime(); - fstate.updateModifyDateTime(0L); + Date modifyDate = new Date( info.getModifyDateTime()); + fstate.updateModifyDateTime(modifyDate.getTime()); } } } @@ -3416,7 +3558,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Debug if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO)) - logger.debug("Set file information - access denied, " + name); + logger.debug("Set file information - access denied, " + name, ex); // Convert to a filesystem access denied status @@ -3553,14 +3695,14 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa if(file.isDirectory()) throw new AccessDeniedException(); - // If the content channel is not open for the file then start a transaction - + // If the content channel is not open for the file then start a transaction + if ( file instanceof ContentNetworkFile) { - ContentNetworkFile contentFile = (ContentNetworkFile) file; - - if ( contentFile.hasContent() == false) - beginReadTransaction( sess); + ContentNetworkFile contentFile = (ContentNetworkFile) file; + + if ( contentFile.hasContent() == false) + beginReadTransaction( sess); } // Read a block of data from the file @@ -3597,21 +3739,21 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa */ public long seekFile(SrvSession sess, TreeConnection tree, NetworkFile file, long pos, int typ) throws IOException { - // Check if the file is a directory - - if ( file.isDirectory()) - throw new AccessDeniedException(); + // Check if the file is a directory + + if ( file.isDirectory()) + throw new AccessDeniedException(); - // If the content channel is not open for the file then start a transaction - - ContentNetworkFile contentFile = (ContentNetworkFile) file; - - if ( contentFile.hasContent() == false) - beginReadTransaction( sess); - - // Set the file position + // If the content channel is not open for the file then start a transaction + + ContentNetworkFile contentFile = (ContentNetworkFile) file; + + if ( contentFile.hasContent() == false) + beginReadTransaction( sess); + + // Set the file position - return file.seekFile(pos, typ); + return file.seekFile(pos, typ); } /** @@ -3630,15 +3772,15 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa public int writeFile(SrvSession sess, TreeConnection tree, NetworkFile file, byte[] buffer, int bufferOffset, int size, long fileOffset) throws IOException { - // If the content channel is not open for the file then start a transaction - - if ( file instanceof ContentNetworkFile) - { - ContentNetworkFile contentFile = (ContentNetworkFile) file; - - if ( contentFile.hasContent() == false) - beginReadTransaction( sess); - } + // If the content channel is not open for the file then start a transaction + + if ( file instanceof ContentNetworkFile) + { + ContentNetworkFile contentFile = (ContentNetworkFile) file; + + if ( contentFile.hasContent() == false) + beginReadTransaction( sess); + } // Check if there is a quota manager @@ -3664,8 +3806,8 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa quotaMgr.allocateSpace(sess, tree, file, extendSize); } } - - // Write to the file + + // Write to the file file.writeFile(buffer, size, bufferOffset, fileOffset); @@ -3714,11 +3856,11 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa if ( fstate != null && fstate.hasFilesystemObject() && fstate.exists() ) { // Check that the node exists - + if (fileFolderService.exists((NodeRef) fstate.getFilesystemObject())) { - // Bump the file states expiry time - + // Bump the file states expiry time + fstate.setExpiryTime(System.currentTimeMillis() + FileState.DefTimeout); // Return the cached noderef @@ -3746,33 +3888,33 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa * @exception FileNotFoundException */ public String getPathForNode( TreeConnection tree, NodeRef nodeRef) - throws FileNotFoundException + throws FileNotFoundException { - // Convert the target node to a path - + // Convert the target node to a path + ContentContext ctx = (ContentContext) tree.getContext(); - List linkPaths = null; - - try { - linkPaths = fileFolderService.getNamePath( ctx.getRootNode(), nodeRef); - } - catch ( org.alfresco.service.cmr.model.FileNotFoundException ex) - { - throw new FileNotFoundException(); - } + List linkPaths = null; + + try { + linkPaths = fileFolderService.getNamePath( ctx.getRootNode(), nodeRef); + } + catch ( org.alfresco.service.cmr.model.FileNotFoundException ex) + { + throw new FileNotFoundException(); + } - // Build the share relative path to the node - - StringBuilder pathStr = new StringBuilder(); - - for ( org.alfresco.service.cmr.model.FileInfo fInfo : linkPaths) { - pathStr.append( FileName.DOS_SEPERATOR); - pathStr.append( fInfo.getName()); - } - - // Return the share relative path - - return pathStr.toString(); + // Build the share relative path to the node + + StringBuilder pathStr = new StringBuilder(); + + for ( org.alfresco.service.cmr.model.FileInfo fInfo : linkPaths) { + pathStr.append( FileName.DOS_SEPERATOR); + pathStr.append( fInfo.getName()); + } + + // Return the share relative path + + return pathStr.toString(); } /** @@ -3825,68 +3967,68 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Nothing to do } - /** - * Return the lock manager used by this filesystem - * - * @param sess SrvSession - * @param tree TreeConnection - * @return LockManager - */ - public LockManager getLockManager(SrvSession sess, TreeConnection tree) { - return _lockManager; - } - - /** - * Return the oplock manager implementation associated with this virtual filesystem - * - * @param sess SrvSession - * @param tree TreeConnection - * @return OpLockManager - */ - public OpLockManager getOpLockManager(SrvSession sess, TreeConnection tree) { - return _lockManager; - } - - /** - * Enable/disable oplock support - * - * @param sess SrvSession - * @param tree TreeConnection - * @return boolean - */ - public boolean isOpLocksEnabled(SrvSession sess, TreeConnection tree) { - + /** + * Return the lock manager used by this filesystem + * + * @param sess SrvSession + * @param tree TreeConnection + * @return LockManager + */ + public LockManager getLockManager(SrvSession sess, TreeConnection tree) { + return _lockManager; + } + + /** + * Return the oplock manager implementation associated with this virtual filesystem + * + * @param sess SrvSession + * @param tree TreeConnection + * @return OpLockManager + */ + public OpLockManager getOpLockManager(SrvSession sess, TreeConnection tree) { + return _lockManager; + } + + /** + * Enable/disable oplock support + * + * @param sess SrvSession + * @param tree TreeConnection + * @return boolean + */ + public boolean isOpLocksEnabled(SrvSession sess, TreeConnection tree) { + // Check if oplocks are enabled ContentContext ctx = (ContentContext) tree.getContext(); return ctx.getDisableOplocks() ? false : true; - } - - /** - * Copy content data from file to file - * - * @param sess SrvSession - * @param tree TreeConnection - * @param fromNode NodeRef - * @param toNode NodeRef - * @param newName String - */ - private void copyContentData( SrvSession sess, TreeConnection tree, NodeRef fromNode, NodeRef toNode, String newName) - { + } + + /** + * Copy content data from file to file + * + * @param sess SrvSession + * @param tree TreeConnection + * @param fromNode NodeRef + * @param toNode NodeRef + * @param newName String + */ + private void copyContentData( SrvSession sess, TreeConnection tree, NodeRef fromNode, NodeRef toNode, String newName) + { ContentData content = (ContentData) nodeService.getProperty(fromNode, ContentModel.PROP_CONTENT); if ( newName != null) content = ContentData.setMimetype( content, mimetypeService.guessMimetype( newName)); nodeService.setProperty(toNode, ContentModel.PROP_CONTENT, content); - } - + } + /** * Clone/move aspects/properties between nodes * - * @param newName String - * @param fromNode NodeRef - * @param toNode NodeRef - * @param ctx ContentContext + * @param newName new name of the file + * @param fromNode NodeRef the node to copy from + * @param toNode NodeRef the node to copy to + * @param ctx Filesystem Context */ private void cloneNodeAspects( String newName, NodeRef fromNode, NodeRef toNode, ContentContext ctx) { @@ -3975,8 +4117,8 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa } // Copy over all other properties from non system namespaces - - for ( Map.Entry entry : nodeService.getProperties(fromNode).entrySet()) { + Map fromProps = nodeService.getProperties(fromNode); + for ( Map.Entry entry : fromProps.entrySet()) { QName propName = entry.getKey(); if (!_excludedNamespaces.contains(propName.getNamespaceURI())) { @@ -4012,10 +4154,24 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa if ( nodeProp != null) nodeService.setProperty( toNode, propName, nodeProp); } + } - private void cloneNode(String newName, NodeRef fromNode, NodeRef toNode, ContentContext ctx) { - cloneNodeAspects(newName, fromNode, toNode, ctx); + /** + * Clone node + * + * @param newName the new name of the node + * @param fromNode the node to copy from + * @param toNode the node to copy to + * @param ctx + */ + private void cloneNode(String newName, NodeRef fromNode, NodeRef toNode, ContentContext ctx) + { + if(logger.isDebugEnabled()) + { + logger.debug("clone node from fromNode:" + fromNode + "toNode:" + toNode); + } + cloneNodeAspects(newName, fromNode, toNode, ctx); // copy over the node creator and owner properties // need to disable the auditable aspect first to prevent default audit behaviour @@ -4023,22 +4179,47 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa try { nodeService.setProperty(toNode, ContentModel.PROP_CREATOR, nodeService.getProperty(fromNode, ContentModel.PROP_CREATOR)); - nodeService.setProperty(toNode, ContentModel.PROP_OWNER, nodeService.getProperty(fromNode, ContentModel.PROP_OWNER)); + ownableService.setOwner(toNode, ownableService.getOwner(fromNode)); } finally { - if(!alreadyDisabled) - { - policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_AUDITABLE); - } + if(!alreadyDisabled) + { + policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_AUDITABLE); + } } - Set permissions = permissionService.getAllSetPermissions(fromNode); - permissionService.deletePermissions(fromNode); - for(AccessPermission permission : permissions) - { - permissionService.setPermission(toNode, permission.getAuthority(), permission.getPermission(), (permission.getAccessStatus() == AccessStatus.ALLOWED)); - } + Set permissions = permissionService.getAllSetPermissions(fromNode); + boolean inheritParentPermissions = permissionService.getInheritParentPermissions(fromNode); + permissionService.deletePermissions(fromNode); + + permissionService.setInheritParentPermissions(toNode, inheritParentPermissions); + for(AccessPermission permission : permissions) + { + permissionService.setPermission(toNode, permission.getAuthority(), permission.getPermission(), (permission.getAccessStatus() == AccessStatus.ALLOWED)); + } + + // Need to take a new guess at the mimetype based upon the new file name. + ContentData content = (ContentData)nodeService.getProperty(toNode, ContentModel.PROP_CONTENT); + + // Take a guess at the mimetype (if it has not been set by something already) + if (content != null && (content.getMimetype() == null || content.getMimetype().equals(MimetypeMap.MIMETYPE_BINARY))) + { + String mimetype = mimetypeService.guessMimetype(newName); + if(logger.isDebugEnabled()) + { + logger.debug("set new mimetype to:" + mimetype); + } + ContentData replacement = ContentData.setMimetype(content, mimetype); + nodeService.setProperty(toNode, ContentModel.PROP_CONTENT, replacement); + } + + // Extract metadata pending change for ALF-5082 + Action action = getActionService().createAction("extract-metadata"); + if(action != null) + { + getActionService().executeAction(action, toNode); + } } /** @@ -4074,37 +4255,58 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa return fstatus; } + private boolean isRenameShuffle(String oldFilename, String newFilename, Pattern renameShufflePattern) + { + boolean renameShuffle = false; + String oldNameLower = oldFilename.toLowerCase(); + String newNameLower = newFilename.toLowerCase(); + renameShuffle = renameShuffle || renameShufflePattern.matcher(oldNameLower).matches(); + renameShuffle = renameShuffle || renameShufflePattern.matcher(newNameLower).matches(); + + return renameShuffle; + } + /** * Get the disk information for this shared disk device. * - * @param ctx DiskDeviceContext - * @param diskDev SrvDiskInfo + * @param ctx DiskDeviceContext + * @param diskDev SrvDiskInfo * @exception IOException */ public void getDiskInformation(DiskDeviceContext ctx, SrvDiskInfo diskDev) throws IOException { - - // Set the block size and blocks per allocation unit - - diskDev.setBlockSize( DiskBlockSize); - diskDev.setBlocksPerAllocationUnit( DiskBlocksPerUnit); - - // Get the free and total disk size in bytes from the content store - - long freeSpace = contentService.getStoreFreeSpace(); - long totalSpace= contentService.getStoreTotalSpace(); - - if ( totalSpace == -1L) { - - // Use a fixed value for the total space, content store does not support size information - - totalSpace = DiskSizeDefault; - freeSpace = DiskFreeDefault; - } + + // Set the block size and blocks per allocation unit + + diskDev.setBlockSize( DiskBlockSize); + diskDev.setBlocksPerAllocationUnit( DiskBlocksPerUnit); + + // Get the free and total disk size in bytes from the content store + + long freeSpace = contentService.getStoreFreeSpace(); + long totalSpace= contentService.getStoreTotalSpace(); + + if ( totalSpace == -1L) { + + // Use a fixed value for the total space, content store does not support size information + + totalSpace = DiskSizeDefault; + freeSpace = DiskFreeDefault; + } - // Convert the total/free space values to allocation units - - diskDev.setTotalUnits( totalSpace / DiskAllocationUnit); - diskDev.setFreeUnits( freeSpace / DiskAllocationUnit); + // Convert the total/free space values to allocation units + + diskDev.setTotalUnits( totalSpace / DiskAllocationUnit); + diskDev.setFreeUnits( freeSpace / DiskAllocationUnit); + } + + public void setActionService(ActionService actionService) + { + this.actionService = actionService; + } + + public ActionService getActionService() + { + return actionService; } } diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java new file mode 100644 index 0000000000..13e9c6af05 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java @@ -0,0 +1,2570 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.filesys.repo; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.transaction.UserTransaction; + +import junit.framework.TestCase; + +import org.alfresco.jlan.server.NetworkServer; +import org.alfresco.jlan.server.SrvSession; +import org.alfresco.jlan.server.config.ServerConfiguration; +import org.alfresco.jlan.server.core.DeviceContext; +import org.alfresco.jlan.server.core.DeviceContextException; +import org.alfresco.jlan.server.core.SharedDevice; +import org.alfresco.jlan.server.filesys.AccessMode; +import org.alfresco.jlan.server.filesys.DiskSharedDevice; +import org.alfresco.jlan.server.filesys.FileAction; +import org.alfresco.jlan.server.filesys.FileAttribute; +import org.alfresco.jlan.server.filesys.FileExistsException; +import org.alfresco.jlan.server.filesys.FileInfo; +import org.alfresco.jlan.server.filesys.FileOpenParams; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.jlan.server.filesys.NetworkFileServer; +import org.alfresco.jlan.server.filesys.SearchContext; +import org.alfresco.jlan.server.filesys.TreeConnection; +import org.alfresco.model.ContentModel; +import org.alfresco.model.ForumModel; +import org.alfresco.repo.action.evaluator.NoConditionEvaluator; +import org.alfresco.repo.management.subsystems.ApplicationContextFactory; +import org.alfresco.repo.model.Repository; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.transfer.TransferModel; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionCondition; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.action.CompositeAction; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.MLText; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.rule.Rule; +import org.alfresco.service.cmr.rule.RuleService; +import org.alfresco.service.cmr.rule.RuleType; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.ClassPathResource; +import org.springframework.extensions.config.element.GenericConfigElement; + +/** + * Unit tests for Alfresco Repository ContentDiskDriver + */ +public class ContentDiskDriverTest extends TestCase +{ + private Repository repositoryHelper; + private CifsHelper cifsHelper; + private ContentDiskDriver driver; + private NodeService mlAwareNodeService; + private NodeService nodeService; + private TransactionService transactionService; + private ContentService contentService; + private RuleService ruleService; + private ActionService actionService; + + private static Log logger = LogFactory.getLog(ContentDiskDriverTest.class); + + final String SHARE_NAME = "test"; + final String STORE_NAME = "workspace://SpacesStore"; + final String ROOT_PATH = "/app:company_home"; + + private ApplicationContext applicationContext; + + private final String TEST_ROOT_PATH="ContentDiskDriverTest"; + private final String TEST_ROOT_DOS_PATH="\\"+TEST_ROOT_PATH; + + @Override + protected void setUp() throws Exception + { + applicationContext = ApplicationContextHelper.getApplicationContext(); + repositoryHelper = (Repository)this.applicationContext.getBean("repositoryHelper"); + ApplicationContextFactory fileServers = (ApplicationContextFactory) this.applicationContext.getBean("fileServers"); + cifsHelper = (CifsHelper) this.applicationContext.getBean("cifsHelper"); + driver = (ContentDiskDriver)this.applicationContext.getBean("contentDiskDriver"); + mlAwareNodeService = (NodeService) this.applicationContext.getBean("mlAwareNodeService"); + nodeService = (NodeService)applicationContext.getBean("nodeService"); + transactionService = (TransactionService)applicationContext.getBean("transactionService"); + contentService = (ContentService)applicationContext.getBean("contentService"); + ruleService = (RuleService)applicationContext.getBean("ruleService"); + actionService = (ActionService)this.applicationContext.getBean("actionService"); + + assertNotNull("content disk driver is null", driver); + assertNotNull("repositoryHelper is null", repositoryHelper); + assertNotNull("mlAwareNodeService is null", mlAwareNodeService); + assertNotNull("nodeService is null", nodeService); + assertNotNull("transactionService is null", transactionService); + assertNotNull("contentService is null", contentService); + assertNotNull("ruleService is null", ruleService); + assertNotNull("actionService is null", actionService); + + AuthenticationUtil.setRunAsUserSystem(); + + // remove our test root + RetryingTransactionCallback removeRootCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NodeRef companyHome = repositoryHelper.getCompanyHome(); + NodeRef rootNode = nodeService.getChildByName(companyHome, ContentModel.ASSOC_CONTAINS, TEST_ROOT_PATH); + if(rootNode != null) + { + logger.debug("Clean up test root node"); + nodeService.deleteNode(rootNode); + } + return null; + } + }; + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + tran.doInTransaction(removeRootCB, false, true); + + } + + @Override + protected void tearDown() throws Exception + { +// UserTransaction txn = transactionService.getUserTransaction(); +// assertNotNull("transaction leaked", txn); +// txn.getStatus(); +// txn.rollback(); + } + + /** + * Test create context. + * + * Must have "store" and "rootPath" + */ + public void testCreateContext() throws Exception + { + logger.debug("testCreateContext"); + + GenericConfigElement cfg1 = new GenericConfigElement("filesystem"); + + GenericConfigElement store = new GenericConfigElement("store"); + store.setValue(STORE_NAME); + cfg1.addChild(store); + + GenericConfigElement rootPath = new GenericConfigElement("rootPath"); + rootPath.setValue(ROOT_PATH); + cfg1.addChild(rootPath); + + /** + * Step 1: Call create context and expect it to succeed + */ + DeviceContext context = driver.createContext(SHARE_NAME, cfg1); + + assertTrue (context instanceof ContentContext); + assertNotNull (context); + + ContentContext ctx = (ContentContext)context; + + assertEquals("Device name wrong", SHARE_NAME, ctx.getDeviceName()); + assertEquals("Root Path wrong", ROOT_PATH, ctx.getRootPath()); + + context.CloseContext(); + + /** + * Step 2: Negative test - missing store property + */ + try + { + GenericConfigElement cfg2 = new GenericConfigElement("filesystem"); + cfg2.addChild(rootPath); + driver.createContext(SHARE_NAME, cfg2); + fail("missing store not detected"); + } + catch (DeviceContextException de) + { + // expect to go here + } + + /** + * Step 3: Negative test - missing rootPath property + */ + try + { + GenericConfigElement cfg2 = new GenericConfigElement("filesystem"); + cfg2.addChild(store); + driver.createContext(SHARE_NAME, cfg2); + fail("missing store not detected"); + } + catch (DeviceContextException de) + { + // expect to go here + } + } + + private DiskSharedDevice getDiskSharedDevice() throws DeviceContextException + { + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + + GenericConfigElement cfg1 = new GenericConfigElement("filesystem"); + + GenericConfigElement store = new GenericConfigElement("store"); + store.setValue(STORE_NAME); + cfg1.addChild(store); + + GenericConfigElement rootPath = new GenericConfigElement("rootPath"); + rootPath.setValue(ROOT_PATH); + cfg1.addChild(rootPath); + + ContentContext filesysContext = (ContentContext) driver.createContext(STORE_NAME, cfg1); + + DiskSharedDevice share = new DiskSharedDevice("test", driver, filesysContext); + + return share; + } + + /** + * Test Create File + */ + public void testCreateFile() throws Exception + { + logger.debug("testCreatedFile"); + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + class TestContext + { + NodeRef testNodeRef; + }; + + final TestContext testContext = new TestContext(); + + /** + * Step 1 : Create a new file in read/write mode and add some content. + */ + int openAction = FileAction.CreateNotExist; + + final String FILE_NAME="testCreateFile.new"; + final String FILE_PATH="\\"+FILE_NAME; + + FileOpenParams params = new FileOpenParams(FILE_PATH, openAction, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + + final NetworkFile file = driver.createFile(testSession, testConnection, params); + assertNotNull("file is null", file); + assertFalse("file is read only, should be read-write", file.isReadOnly()); + + RetryingTransactionCallback writeStuffCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + byte[] stuff = "Hello World".getBytes(); + file.writeFile(stuff, stuff.length, 0, 0); + file.close(); // needed to actually flush content to node + return null; + } + }; + tran.doInTransaction(writeStuffCB); + + RetryingTransactionCallback validateCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NodeRef companyHome = repositoryHelper.getCompanyHome(); + NodeRef newNode = nodeService.getChildByName(companyHome, ContentModel.ASSOC_CONTAINS, FILE_NAME); + testContext.testNodeRef = newNode; + assertNotNull("can't find new node", newNode); + Serializable content = nodeService.getProperty(newNode, ContentModel.PROP_CONTENT); + assertNotNull("content is null", content); + return null; + } + }; + tran.doInTransaction(validateCB); + + // now validate that the new node is in the correct location and has the correct name + FileInfo info = driver.getFileInformation(testSession, testConnection, FILE_PATH); + assertNotNull("info is null", info); + + NodeRef n2 = driver.getNodeForPath(testConnection, FILE_PATH); + assertEquals("get Node For Path returned different node", testContext.testNodeRef, n2); + + /** + * Step 2 : Negative Test Attempt to create the same file again + */ + try + { + driver.createFile(testSession, testConnection, params); + fail("File exists not detected"); + } + catch (FileExistsException fe) + { + // expect to go here + } + + // Clean up so we could run the test again + driver.deleteFile(testSession, testConnection, FILE_PATH); + + /** + * Step 3 : create a file in a new directory in read only mode + */ + String FILE2_PATH = TEST_ROOT_DOS_PATH + FILE_PATH; + + FileOpenParams dirParams = new FileOpenParams(TEST_ROOT_DOS_PATH, openAction, AccessMode.ReadOnly, FileAttribute.NTDirectory, 0); + driver.createDirectory(testSession, testConnection, dirParams); + + FileOpenParams file2Params = new FileOpenParams(FILE2_PATH, openAction, AccessMode.ReadOnly, FileAttribute.NTNormal, 0); + NetworkFile file2 = driver.createFile(testSession, testConnection, file2Params); + + // clean up so we could run the test again + driver.deleteFile(testSession, testConnection, FILE2_PATH); + } + + /** + * Unit test of delete file + */ + public void testDeleteFile() throws Exception + { + logger.debug("testDeleteFile"); + + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + + GenericConfigElement cfg1 = new GenericConfigElement("filesystem"); + + GenericConfigElement store = new GenericConfigElement("store"); + store.setValue(STORE_NAME); + cfg1.addChild(store); + + GenericConfigElement rootPath = new GenericConfigElement("rootPath"); + rootPath.setValue(ROOT_PATH); + cfg1.addChild(rootPath); + + ContentContext filesysContext = (ContentContext) driver.createContext(STORE_NAME, cfg1); + + DiskSharedDevice share = new DiskSharedDevice("test", driver, filesysContext); + TreeConnection testConnection = testServer.getTreeConnection(share); + + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + + /** + * Step 1 : Create a new file in read/write mode and add some content. + */ + int openAction = FileAction.CreateNotExist; + String FILE_PATH="\\testDeleteFile.new"; + + FileOpenParams params = new FileOpenParams(FILE_PATH, openAction, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + + final NetworkFile file = driver.createFile(testSession, testConnection, params); + assertNotNull("file is null", file); + assertFalse("file is read only, should be read-write", file.isReadOnly()); + + RetryingTransactionCallback writeStuffCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + byte[] stuff = "Hello World".getBytes(); + file.writeFile(stuff, stuff.length, 0, 0); + file.close(); // needed to actually flush content to node + return null; + } + }; + tran.doInTransaction(writeStuffCB); + + /** + * Step 1: Delete file by path + */ + driver.deleteFile(testSession, testConnection, FILE_PATH); + + /** + * Step 2: Negative test - Delete file again + */ + try + { + driver.deleteFile(testSession, testConnection, FILE_PATH); + fail("delete a non existent file"); + } + catch (IOException fe) + { + // expect to go here + } + } + + /** + * Test Set Info + * + * Three flags set + *
    + *
  1. SetDeleteOnClose
  2. + *
  3. SetCreationDate
  4. + *
  5. SetModifyDate
  6. + *
+ */ + /* + * MER : I can't see what DeleteOnClose does. Test commented out + */ + public void testSetFileInfo() throws Exception + { + logger.debug("testSetFileInfo"); + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + final SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + final TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + Date now = new Date(); + + // CREATE 6 hours ago + final Date CREATED = new Date(now.getTime() - 1000 * 60 * 60 * 6); + // Modify one hour ago + final Date MODIFIED = new Date(now.getTime() - 1000 * 60 * 60 * 1); + + class TestContext + { + NodeRef testNodeRef; + }; + + final TestContext testContext = new TestContext(); + + /** + * Step 1 : Create a new file in read/write mode and add some content. + * Call SetInfo to set the creation date + */ + int openAction = FileAction.CreateNotExist; + + final String FILE_NAME="testSetFileInfo.txt"; + final String FILE_PATH="\\"+FILE_NAME; + + final FileOpenParams params = new FileOpenParams(FILE_PATH, openAction, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + + final NetworkFile file = driver.createFile(testSession, testConnection, params); + assertNotNull("file is null", file); + assertFalse("file is read only, should be read-write", file.isReadOnly()); + + RetryingTransactionCallback writeStuffCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + byte[] stuff = "Hello World".getBytes(); + file.writeFile(stuff, stuff.length, 0, 0); + file.close(); // needed to actually flush content to node + + FileInfo info = driver.getFileInformation(testSession, testConnection, FILE_PATH); + info.setFileInformationFlags(FileInfo.SetModifyDate); + info.setModifyDateTime(MODIFIED.getTime()); + driver.setFileInformation(testSession, testConnection, FILE_PATH, info); + return null; + } + }; + tran.doInTransaction(writeStuffCB); + + RetryingTransactionCallback validateCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NodeRef companyHome = repositoryHelper.getCompanyHome(); + NodeRef newNode = nodeService.getChildByName(companyHome, ContentModel.ASSOC_CONTAINS, FILE_NAME); + testContext.testNodeRef = newNode; + assertNotNull("can't find new node", newNode); + Serializable content = nodeService.getProperty(newNode, ContentModel.PROP_CONTENT); + assertNotNull("content is null", content); + Date modified = (Date)nodeService.getProperty(newNode, ContentModel.PROP_MODIFIED); + assertEquals("modified time not set correctly", MODIFIED, modified); + return null; + } + }; + tran.doInTransaction(validateCB); + + /** + * Step 2: Change the created date + */ + logger.debug("Step 2: Change the created date"); + RetryingTransactionCallback changeCreatedCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + FileInfo info = driver.getFileInformation(testSession, testConnection, FILE_PATH); + info.setFileInformationFlags(FileInfo.SetCreationDate); + info.setCreationDateTime(CREATED.getTime()); + driver.setFileInformation(testSession, testConnection, FILE_PATH, info); + return null; + } + }; + tran.doInTransaction(changeCreatedCB); + + RetryingTransactionCallback validateCreatedCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NodeRef companyHome = repositoryHelper.getCompanyHome(); + NodeRef newNode = nodeService.getChildByName(companyHome, ContentModel.ASSOC_CONTAINS, FILE_NAME); + testContext.testNodeRef = newNode; + assertNotNull("can't find new node", newNode); + Serializable content = nodeService.getProperty(newNode, ContentModel.PROP_CONTENT); + assertNotNull("content is null", content); + Date created = (Date)nodeService.getProperty(newNode, ContentModel.PROP_CREATED); + assertEquals("created time not set correctly", CREATED, created); + return null; + } + }; + tran.doInTransaction(validateCreatedCB); + +// /** +// * Step 3: Test +// */ +// logger.debug("Step 3: test deleteOnClose"); +// RetryingTransactionCallback deleteOnCloseCB = new RetryingTransactionCallback() { +// +// @Override +// public Void execute() throws Throwable +// { +// NetworkFile f2 = driver.openFile(testSession, testConnection, params); +// +// FileInfo info = driver.getFileInformation(testSession, testConnection, FILE_PATH); +// info.setFileInformationFlags(FileInfo.SetDeleteOnClose); +// driver.setFileInformation(testSession, testConnection, FILE_PATH, info); +// +// byte[] stuff = "Update".getBytes(); +// f2.writeFile(stuff, stuff.length, 0, 0); +// f2.close(); // needed to actually flush content to node +// +// return null; +// } +// }; +// tran.doInTransaction(deleteOnCloseCB); +// +// RetryingTransactionCallback validateDeleteOnCloseCB = new RetryingTransactionCallback() { +// +// @Override +// public Void execute() throws Throwable +// { +// NodeRef companyHome = repositoryHelper.getCompanyHome(); +// NodeRef newNode = nodeService.getChildByName(companyHome, ContentModel.ASSOC_CONTAINS, FILE_NAME); +// assertNull("can still find new node", newNode); +// return null; +// } +// }; +// tran.doInTransaction(validateDeleteOnCloseCB); + + // clean up so we could run the test again + driver.deleteFile(testSession, testConnection, FILE_PATH); + + } // test set file info + + + /** + * Test Open File + * + * MER DISABLED TEST 22/03/2011 won't run. + */ + public void DISABLED_testOpenFile() throws Exception + { + logger.debug("testOpenFile"); + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + final String FILE_NAME="testOpenFileY.whatever"; + + /** + * Step 1 : Negative test - try to open a file that does not exist + */ + String FILE_PATH="\\" + FILE_NAME; + + FileOpenParams params = new FileOpenParams(FILE_PATH, FileAction.CreateNotExist, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + try + { + NetworkFile file = driver.openFile(testSession, testConnection, params); + fail ("managed to open non existant file!"); + } + catch (IOException ie) + { + // expect to go here + } + + /** + * Step 2: Now create the file through the node service and open it. + */ + RetryingTransactionCallback createFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NodeRef companyHome = repositoryHelper.getCompanyHome(); + ChildAssociationRef ref = nodeService.createNode(companyHome, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, FILE_NAME), ContentModel.TYPE_CONTENT); + nodeService.setProperty(ref.getChildRef(), ContentModel.PROP_NAME, FILE_NAME); + return null; + } + }; + tran.doInTransaction(createFileCB, false, true); + + NetworkFile file = driver.openFile(testSession, testConnection, params); + assertNotNull(file); + + driver.deleteFile(testSession, testConnection, FILE_PATH); + } // testOpenFile + + + /** + * Unit test of file exists + */ + public void testFileExists() throws Exception + { + logger.debug("testFileExists"); + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + String FILE_PATH="\\testFileExists.new"; + + /** + * Step 1 : Call FileExists for a file which does not exist + */ + int status = driver.fileExists(testSession, testConnection, FILE_PATH); + assertEquals(status, 0); + + /** + * Step 2: Create a new file in read/write mode and add some content. + */ + int openAction = FileAction.CreateNotExist; + + FileOpenParams params = new FileOpenParams(FILE_PATH, openAction, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + + final NetworkFile file = driver.createFile(testSession, testConnection, params); + assertNotNull("file is null", file); + assertFalse("file is read only, should be read-write", file.isReadOnly()); + + RetryingTransactionCallback createFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + byte[] stuff = "Hello World".getBytes(); + file.writeFile(stuff, stuff.length, 0, 0); + file.close(); + + return null; + } + }; + tran.doInTransaction(createFileCB, false, true); + + status = driver.fileExists(testSession, testConnection, FILE_PATH); + assertEquals(status, 1); + + /** + * Step 3 : Delete the node - check status goes back to 0 + */ + driver.deleteFile(testSession, testConnection, FILE_PATH); + + status = driver.fileExists(testSession, testConnection, FILE_PATH); + assertEquals(status, 0); + + // BODGE - there's a dangling transaction that needs getting rid of + // Work around for ALF-7674 + UserTransaction txn = transactionService.getUserTransaction(); + assertNotNull("transaction leaked", txn); + txn.getStatus(); + txn.rollback(); + + + } // testFileExists + + /** + * Unit test of rename file + */ + public void testRenameFile() throws Exception + { + logger.debug("testRenameFile"); + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + final TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + final String FILE_PATH1=TEST_ROOT_DOS_PATH + "\\SourceFile1.new"; + final String FILE_NAME2 = "SourceFile2.new"; + final String FILE_PATH2=TEST_ROOT_DOS_PATH +"\\" + FILE_NAME2; + final String FILE_PATH3=TEST_ROOT_DOS_PATH +"\\SourceFile3.new"; + + FileOpenParams dirParams = new FileOpenParams(TEST_ROOT_DOS_PATH, 0, AccessMode.ReadOnly, FileAttribute.NTDirectory, 0); + driver.createDirectory(testSession, testConnection, dirParams); + + FileOpenParams params1 = new FileOpenParams(FILE_PATH1, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + final NetworkFile file1 = driver.createFile(testSession, testConnection, params1); + + FileOpenParams params3 = new FileOpenParams(FILE_PATH3, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + final NetworkFile file3 = driver.createFile(testSession, testConnection, params3); + + /** + * Step 1 : Negative test, Call Rename for a file which does not exist + */ + try + { + driver.renameFile(testSession, testConnection, "\\Wibble\\wobble", FILE_PATH1); + fail("rename did not detect missing file"); + } + catch (IOException e) + { + // expect to go here + } + + /** + * Step 2: Negative test, Call Rename for a destination that does not exist. + */ + try + { + driver.renameFile(testSession, testConnection, FILE_PATH1, "\\wibble\\wobble"); + fail("rename did not detect missing file"); + } + catch (IOException e) + { + // expect to go here + } + + /** + * Step 3: Rename a file to a destination that is a file rather than a directory + */ + try + { + driver.renameFile(testSession, testConnection, FILE_PATH1, FILE_PATH3); + fail("rename did not detect missing file"); + } + catch (IOException e) + { + // expect to go here + } + + /** + * Step 4: Successfully rename a file - check the name, props and content. + */ + final String LAST_NAME= "Bloggs"; + + RetryingTransactionCallback setPropertiesCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + final NodeRef file1NodeRef = driver.getNodeForPath(testConnection, FILE_PATH1); + assertNotNull("node ref not found", file1NodeRef); + nodeService.setProperty(file1NodeRef, ContentModel.PROP_LASTNAME, LAST_NAME); + + return null; + } + }; + tran.doInTransaction(setPropertiesCB, false, true); + + driver.renameFile(testSession, testConnection, FILE_PATH1, FILE_PATH2); + + RetryingTransactionCallback validateCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NodeRef file2NodeRef = driver.getNodeForPath(testConnection, FILE_PATH2); + //assertEquals("node ref has changed on a rename", file1NodeRef, file2NodeRef); + assertEquals(nodeService.getProperty(file2NodeRef, ContentModel.PROP_LASTNAME), LAST_NAME); + ChildAssociationRef parentRef = nodeService.getPrimaryParent(file2NodeRef); + assertTrue("file has wrong assoc local name", parentRef.getQName().getLocalName().equals(FILE_NAME2)); + assertTrue("not primary assoc", parentRef.isPrimary()); + + return null; + } + }; + tran.doInTransaction(validateCB, false, true); + + /** + * Step 5: Rename to another directory + */ + String DIR_NEW_PATH = TEST_ROOT_DOS_PATH + "\\NewDir"; + String NEW_PATH = DIR_NEW_PATH + "\\File2"; + FileOpenParams params5 = new FileOpenParams(DIR_NEW_PATH, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + driver.createDirectory(testSession, testConnection, params5); + + NodeRef newDirNodeRef = driver.getNodeForPath(testConnection, DIR_NEW_PATH); + + driver.renameFile(testSession, testConnection, FILE_PATH2, NEW_PATH); + + NodeRef file5NodeRef = driver.getNodeForPath(testConnection, NEW_PATH); + ChildAssociationRef parentRef5 = nodeService.getPrimaryParent(file5NodeRef); + + assertTrue(parentRef5.getParentRef().equals(newDirNodeRef)); + +// /** +// * Step 5: rename to self - check no damage. +// */ +// try +// { +// driver.renameFile(testSession, testConnection, FILE_PATH2, FILE_PATH2); +// fail("rename did not detect rename to self"); +// } +// catch (IOException e) +// { + // expect to go here +// } + + } // testRenameFile + + + /** + * Unit test of rename versionable file + */ + public void testScenarioRenameVersionableFile() throws Exception + { + logger.debug("testScenarioRenameVersionableFile"); + + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + final TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + final String FILE_PATH1=TEST_ROOT_DOS_PATH + "\\SourceFile1.new"; + final String FILE_PATH2=TEST_ROOT_DOS_PATH + "\\SourceFile2.new"; + + class TestContext + { + }; + + final TestContext testContext = new TestContext(); + + FileOpenParams dirParams = new FileOpenParams(TEST_ROOT_DOS_PATH, 0, AccessMode.ReadOnly, FileAttribute.NTDirectory, 0); + driver.createDirectory(testSession, testConnection, dirParams); + + FileOpenParams params1 = new FileOpenParams(FILE_PATH1, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + NetworkFile file1 = driver.createFile(testSession, testConnection, params1); + + /** + * Make Node 1 versionable + */ + final String LAST_NAME= "Bloggs"; + + RetryingTransactionCallback makeVersionableCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NodeRef file1NodeRef = driver.getNodeForPath(testConnection, FILE_PATH1); + nodeService.addAspect(file1NodeRef, ContentModel.ASPECT_VERSIONABLE, null); + + ContentWriter contentWriter2 = contentService.getWriter(file1NodeRef, ContentModel.PROP_CONTENT, true); + contentWriter2.putContent("test rename versionable"); + + nodeService.setProperty(file1NodeRef, ContentModel.PROP_LASTNAME, LAST_NAME); + nodeService.setProperty(file1NodeRef, TransferModel.PROP_ENDPOINT_PROTOCOL, "http"); + + return null; + } + }; + tran.doInTransaction(makeVersionableCB, false, true); + + /** + * Step 1: Successfully rename a versionable file - check the name, props and content. + * TODO Check primary assoc, peer assocs, child assocs, modified date, created date, nodeid, permissions. + */ + driver.renameFile(testSession, testConnection, FILE_PATH1, FILE_PATH2); + + RetryingTransactionCallback validateVersionableCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NodeRef file2NodeRef = driver.getNodeForPath(testConnection, FILE_PATH2); + assertNotNull("file2 node ref is null", file2NodeRef); + //assertEquals(nodeService.getProperty(file2NodeRef, ContentModel.PROP_LASTNAME), LAST_NAME); + assertTrue("does not have versionable aspect", nodeService.hasAspect(file2NodeRef, ContentModel.ASPECT_VERSIONABLE)); + assertTrue("sample property is null", nodeService.getProperty(file2NodeRef, TransferModel.PROP_ENDPOINT_PROTOCOL) != null); + + return null; + } + }; + tran.doInTransaction(validateVersionableCB, false, true); + + } // testRenameVersionable + + + /** + * This test tries to simulate the shuffling that is done by MS Word 2003 upon file save + * + * a) TEST.DOC + * b) Save to ~WRDnnnn.TMP + * c) Delete ~WRLnnnn.TMP + * d) Rename TEST.DOC ~WDLnnnn.TMP + * e) Delete TEST.DOC + * f) Rename ~WRDnnnn.TMP to TEST.DOC + * g) Delete ~WRLnnnn.TMP + * + * We need to check that properties, aspects, primary assocs, secondary assocs, peer assocs, node type, + * version history, creation date are maintained. + */ + public void testScenarioMSWord2003SaveShuffle() throws Exception + { + logger.debug("testScenarioMSWord2003SaveShuffle"); + final String FILE_NAME = "TEST.DOC"; + final String FILE_TITLE = "Test document"; + final String FILE_DESCRIPTION = "This is a test document to test CIFS shuffle"; + final String FILE_OLD_TEMP = "~WRL0002.TMP"; + final String FILE_NEW_TEMP = "~WRD0002.TMP"; + + final QName RESIDUAL_MTTEXT = QName.createQName("{gsxhjsx}", "whatever"); + + class TestContext + { + NetworkFile firstFileHandle; + NetworkFile newFileHandle; + NetworkFile oldFileHandle; + + NodeRef testNodeRef; // node ref of test.doc + + Serializable testCreatedDate; + }; + + final TestContext testContext = new TestContext(); + + final String TEST_DIR = TEST_ROOT_DOS_PATH + "\\testScenarioMSWord2003SaveShuffle"; + + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + final SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + final TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + /** + * Clean up just in case garbage is left from a previous run + */ + RetryingTransactionCallback deleteGarbageFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.deleteFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NAME); + return null; + } + }; + + /** + * Create a file in the test directory + */ + + try + { + tran.doInTransaction(deleteGarbageFileCB); + } + catch (Exception e) + { + // expect to go here + } + + RetryingTransactionCallback createFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + + /** + * Create the test directory we are going to use + */ + FileOpenParams createRootDirParams = new FileOpenParams(TEST_ROOT_DOS_PATH, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + FileOpenParams createDirParams = new FileOpenParams(TEST_DIR, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + driver.createDirectory(testSession, testConnection, createRootDirParams); + driver.createDirectory(testSession, testConnection, createDirParams); + + /** + * Create the file we are going to use + */ + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NAME, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.firstFileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull(testContext.firstFileHandle); + + // now load up the node with lots of other stuff that we will test to see if it gets preserved during the + // shuffle. + testContext.testNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + + // test non CM namespace property + nodeService.setProperty(testContext.testNodeRef, TransferModel.PROP_ENABLED, true); + // test CM property not related to an aspect + nodeService.setProperty(testContext.testNodeRef, ContentModel.PROP_ADDRESSEE, "Fred"); + + nodeService.setProperty(testContext.testNodeRef, ContentModel.PROP_TITLE, FILE_TITLE); + nodeService.setProperty(testContext.testNodeRef, ContentModel.PROP_DESCRIPTION, FILE_DESCRIPTION); + + /** + * MLText value - also a residual value in a non cm namespace + */ + MLText mltext = new MLText(); + mltext.addValue(Locale.FRENCH, "Bonjour"); + mltext.addValue(Locale.ENGLISH, "Hello"); + mltext.addValue(Locale.ITALY, "Buongiorno"); + mlAwareNodeService.setProperty(testContext.testNodeRef, RESIDUAL_MTTEXT, mltext); + + // classifiable chosen since its not related to any properties. + nodeService.addAspect(testContext.testNodeRef, ContentModel.ASPECT_CLASSIFIABLE, null); + //nodeService.createAssociation(testContext.testNodeRef, targetRef, assocTypeQName); + + return null; + } + }; + tran.doInTransaction(createFileCB, false, true); + + /** + * Write some content to the test file + */ + RetryingTransactionCallback writeFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + String testContent = "MS Word 2003 shuffle test"; + byte[] testContentBytes = testContent.getBytes(); + testContext.firstFileHandle.writeFile(testContentBytes, testContentBytes.length, 0, 0); + testContext.firstFileHandle.close(); + + testContext.testCreatedDate = nodeService.getProperty(testContext.testNodeRef, ContentModel.PROP_CREATED); + + MLText multi = (MLText)mlAwareNodeService.getProperty(testContext.testNodeRef, RESIDUAL_MTTEXT) ; + multi.getValues(); + + + return null; + } + }; + tran.doInTransaction(writeFileCB, false, true); + + /** + * b) Save the new file + */ + RetryingTransactionCallback saveNewFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NEW_TEMP, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.newFileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull(testContext.newFileHandle); + String testContent = "MS Word 2003 shuffle test This is new content"; + byte[] testContentBytes = testContent.getBytes(); + testContext.newFileHandle.writeFile(testContentBytes, testContentBytes.length, 0, 0); + testContext.newFileHandle.close(); + + return null; + } + }; + tran.doInTransaction(saveNewFileCB, false, true); + + /** + * rename the old file + */ + RetryingTransactionCallback renameOldFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.renameFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NAME, TEST_DIR + "\\" + FILE_OLD_TEMP); + return null; + } + }; + tran.doInTransaction(renameOldFileCB, false, true); + + + RetryingTransactionCallback validateOldFileGoneCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + try + { + driver.deleteFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NAME); + } + catch (IOException e) + { + // expect to go here since previous step renamed the file. + } + + return null; + } + }; + tran.doInTransaction(validateOldFileGoneCB, false, true); + + /** + * Move the new file into place, stuff should get shuffled + */ + RetryingTransactionCallback moveNewFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.renameFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NEW_TEMP, TEST_DIR + "\\" + FILE_NAME); + return null; + } + }; + + tran.doInTransaction(moveNewFileCB, false, true); + + RetryingTransactionCallback validateCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NodeRef shuffledNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + + Map props = nodeService.getProperties(shuffledNodeRef); + + // Check trx:enabled has been shuffled. + assertTrue("node does not contain shuffled ENABLED property", props.containsKey(TransferModel.PROP_ENABLED)); + // check my residual MLText has been transferred + assertTrue(props.containsKey(RESIDUAL_MTTEXT)); + + // Check the titled aspect is correct + assertEquals("name wrong", FILE_NAME, nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_NAME) ); + assertEquals("title wrong", FILE_TITLE, nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_TITLE) ); + assertEquals("description wrong", FILE_DESCRIPTION, nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_DESCRIPTION) ); + + // commented out due to ALF-7641 + // CIFS shuffle, does not preseve MLText values. + // Map mlProps = mlAwareNodeService.getProperties(shuffledNodeRef); + + // MLText multi = (MLText)mlAwareNodeService.getProperty(shuffledNodeRef, RESIDUAL_MTTEXT) ; + // multi.getValues(); + + // check auditable properties + // commented out due to ALF-7635 + // assertEquals("creation date not preserved", ((Date)testContext.testCreatedDate).getTime(), ((Date)nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_CREATED)).getTime()); + + // commented out due to ALF-7628 + // assertEquals("ADDRESSEE PROPERTY Not copied", "Fred", nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_ADDRESSEE)); + // assertTrue("CLASSIFIABLE aspect not present", nodeService.hasAspect(shuffledNodeRef, ContentModel.ASPECT_CLASSIFIABLE)); + + // commented out due to ALF-7584. + // assertEquals("noderef changed", testContext.testNodeRef, shuffledNodeRef); + return null; + } + }; + + tran.doInTransaction(validateCB, true, true); + + /** + * Clean up just in case garbage is left from a previous run + */ + RetryingTransactionCallback deleteOldFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.deleteFile(testSession, testConnection, TEST_DIR + "\\" + FILE_OLD_TEMP); + return null; + } + }; + + tran.doInTransaction(deleteOldFileCB, false, true); + + } // testScenarioMSWord2003SaveShuffle + + + /** + * This test tries to simulate the shuffling that is done by MS Word 2003 + * with backup enabled upon file save + * + * a) TEST.DOC + * b) Save to ~WRDnnnn.TMP + * c) Delete "Backup of TEST.DOC" + * d) Rename TEST.DOC to "Backup of TEST.DOC" + * e) Delete TEST.DOC + * f) Rename ~WRDnnnn.TMP to TEST.DOC + * + * We need to check that properties, aspects, primary assocs, secondary assocs, peer assocs, node type, + * version history, creation date are maintained. + */ + public void testScenarioMSWord2003SaveShuffleWithBackup() throws Exception + { + logger.debug("testScenarioMSWord2003SaveShuffleWithBackup"); + final String FILE_NAME = "TEST.DOC"; + final String FILE_OLD_TEMP = "Backup of TEST.DOC"; + final String FILE_NEW_TEMP = "~WRD0002.TMP"; + + class TestContext + { + NetworkFile firstFileHandle; + NetworkFile newFileHandle; + NodeRef testNodeRef; // node ref of test.doc + }; + + final TestContext testContext = new TestContext(); + + final String TEST_ROOT_DIR = "\\ContentDiskDriverTest"; + final String TEST_DIR = "\\ContentDiskDriverTest\\testScenarioMSWord2003SaveShuffleWithBackup"; + + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + final SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + final TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + /** + * Create a file in the test directory + */ + RetryingTransactionCallback createFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + /** + * Create the test directory we are going to use + */ + FileOpenParams createRootDirParams = new FileOpenParams(TEST_ROOT_DIR, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + FileOpenParams createDirParams = new FileOpenParams(TEST_DIR, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + driver.createDirectory(testSession, testConnection, createRootDirParams); + driver.createDirectory(testSession, testConnection, createDirParams); + + /** + * Create the file we are going to use + */ + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NAME, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.firstFileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull(testContext.firstFileHandle); + + // now load up the node with lots of other stuff that we will test to see if it gets preserved during the + // shuffle. + testContext.testNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + // test non CM namespace property + nodeService.setProperty(testContext.testNodeRef, TransferModel.PROP_ENABLED, true); + // test CM property not related to an aspect + nodeService.setProperty(testContext.testNodeRef, ContentModel.PROP_ADDRESSEE, "Fred"); + nodeService.getProperty(testContext.testNodeRef, ContentModel.PROP_CREATED); + // classifiable chosen since its not related to any properties. + nodeService.addAspect(testContext.testNodeRef, ContentModel.ASPECT_CLASSIFIABLE, null); + //nodeService.createAssociation(testContext.testNodeRef, targetRef, assocTypeQName); + + return null; + } + }; + tran.doInTransaction(createFileCB, false, true); + + /** + * Write some content to the test file + */ + RetryingTransactionCallback writeFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + String testContent = "MS Word 2003 shuffle test"; + byte[] testContentBytes = testContent.getBytes(); + testContext.firstFileHandle.writeFile(testContentBytes, testContentBytes.length, 0, 0); + testContext.firstFileHandle.close(); + return null; + } + }; + tran.doInTransaction(writeFileCB, false, true); + + /** + * b) Save the new file + */ + RetryingTransactionCallback saveNewFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NEW_TEMP, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.newFileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull(testContext.newFileHandle); + String testContent = "MS Word 2003 shuffle test This is new content"; + byte[] testContentBytes = testContent.getBytes(); + testContext.newFileHandle.writeFile(testContentBytes, testContentBytes.length, 0, 0); + testContext.newFileHandle.close(); + + return null; + } + }; + tran.doInTransaction(saveNewFileCB, false, true); + + /** + * rename the old file + */ + RetryingTransactionCallback renameOldFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.renameFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NAME, TEST_DIR + "\\" + FILE_OLD_TEMP); + return null; + } + }; + tran.doInTransaction(renameOldFileCB, false, true); + + + RetryingTransactionCallback validateOldFileGoneCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + try + { + driver.deleteFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NAME); + } + catch (IOException e) + { + // expect to go here since previous step renamed the file. + } + + return null; + } + }; + tran.doInTransaction(validateOldFileGoneCB, false, true); + + /** + * Move the new file into place, stuff should get shuffled + */ + RetryingTransactionCallback moveNewFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.renameFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NEW_TEMP, TEST_DIR + "\\" + FILE_NAME); + return null; + } + }; + + tran.doInTransaction(moveNewFileCB, false, true); + + RetryingTransactionCallback validateCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NodeRef shuffledNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + + Map props = nodeService.getProperties(shuffledNodeRef); + assertTrue("node does not contain shuffled ENABLED property", props.containsKey(TransferModel.PROP_ENABLED)); + + assertEquals("name wrong", FILE_NAME, nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_NAME) ); + + // commented out due to ALF-7628 + //assertEquals("ADDRESSEE PROPERTY Not copied", "Fred", nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_ADDRESSEE)); + //assertEquals("created date changed", testContext.testCreatedDate, (Date)nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_CREATED)); + + // assertTrue("CLASSIFIABLE aspect not present", nodeService.hasAspect(shuffledNodeRef, ContentModel.ASPECT_CLASSIFIABLE)); + + //assertEquals("noderef changed", testContext.testNodeRef, shuffledNodeRef); + return null; + } + }; + + tran.doInTransaction(validateCB, false, true); + + } // testScenarioMSWord2003SaveShuffleWithBackup + + /** + * This test tries to simulate the cifs shuffling that is done to + * support MS Word 2007 + * + * a) TEST.DOCX + * b) Save new to 00000001.TMP + * c) Rename TEST.DOCX to 00000002.TMP + * d) Rename 000000001.TMP to TEST.DOCX + * e) Delete 000000002.TMP + */ + public void testScenarioMSWord2007Save() throws Exception + { + logger.debug("testScenarioMSWord2007SaveShuffle"); + final String FILE_NAME = "TEST.DOCX"; + final String FILE_OLD_TEMP = "00000001.TMP"; + final String FILE_NEW_TEMP = "00000002.TMP"; + + class TestContext + { + NetworkFile firstFileHandle; + NetworkFile newFileHandle; + NodeRef testNodeRef; // node ref of test.doc + }; + + final TestContext testContext = new TestContext(); + + final String TEST_ROOT_DIR = "\\ContentDiskDriverTest"; + final String TEST_DIR = "\\ContentDiskDriverTest\\testScenarioMSWord2007Save"; + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + final SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + final TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + /** + * Create a file in the test directory + */ + RetryingTransactionCallback createFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + /** + * Create the test directory we are going to use + */ + FileOpenParams createRootDirParams = new FileOpenParams(TEST_ROOT_DIR, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + FileOpenParams createDirParams = new FileOpenParams(TEST_DIR, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + driver.createDirectory(testSession, testConnection, createRootDirParams); + driver.createDirectory(testSession, testConnection, createDirParams); + + /** + * Create the file we are going to use + */ + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NAME, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.firstFileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull(testContext.firstFileHandle); + + // no need to test lots of different properties, that's already been tested above + testContext.testNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + nodeService.setProperty(testContext.testNodeRef, TransferModel.PROP_ENABLED, true); + + return null; + } + }; + tran.doInTransaction(createFileCB, false, true); + + /** + * a) Write some content to the test file + */ + RetryingTransactionCallback writeFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + String testContent = "MS Word 2007 shuffle test"; + byte[] testContentBytes = testContent.getBytes(); + testContext.firstFileHandle.writeFile(testContentBytes, testContentBytes.length, 0, 0); + testContext.firstFileHandle.close(); + return null; + } + }; + tran.doInTransaction(writeFileCB, false, true); + + /** + * b) Save the new file + */ + RetryingTransactionCallback saveNewFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NEW_TEMP, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.newFileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull(testContext.newFileHandle); + String testContent = "MS Word 2007 shuffle test This is new content"; + byte[] testContentBytes = testContent.getBytes(); + testContext.newFileHandle.writeFile(testContentBytes, testContentBytes.length, 0, 0); + testContext.newFileHandle.close(); + + return null; + } + }; + tran.doInTransaction(saveNewFileCB, false, true); + + /** + * c) rename the old file + */ + RetryingTransactionCallback renameOldFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.renameFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NAME, TEST_DIR + "\\" + FILE_OLD_TEMP); + return null; + } + }; + tran.doInTransaction(renameOldFileCB, false, true); + + + /** + * d) Move the new file into place, stuff should get shuffled + */ + RetryingTransactionCallback moveNewFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.renameFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NEW_TEMP, TEST_DIR + "\\" + FILE_NAME); + return null; + } + }; + + tran.doInTransaction(moveNewFileCB, false, true); + + RetryingTransactionCallback deleteOldFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.deleteFile(testSession, testConnection, TEST_DIR + "\\" + FILE_OLD_TEMP); + return null; + } + }; + tran.doInTransaction(deleteOldFileCB, false, true); + + RetryingTransactionCallback validateCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NodeRef shuffledNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + + Map props = nodeService.getProperties(shuffledNodeRef); + assertTrue("node does not contain shuffled ENABLED property", props.containsKey(TransferModel.PROP_ENABLED)); + assertEquals("name wrong", FILE_NAME, nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_NAME) ); + return null; + } + }; + + tran.doInTransaction(validateCB, false, true); + + } // testScenarioWord2007 save + + /** + * This test tries to simulate the cifs shuffling that is done to + * support EMACS + * + * a) emacsTest.txt + * b) Rename original file to emacsTest.txt~ + * c) Create emacsTest.txt + */ + public void DISABLED_testScenarioEmacsSave() throws Exception + { + logger.debug("testScenarioEmacsSave"); + final String FILE_NAME = "emacsTest.txt"; + final String FILE_OLD_TEMP = "emacsTest.txt~"; + + class TestContext + { + NetworkFile firstFileHandle; + NetworkFile newFileHandle; + NodeRef testNodeRef; // node ref of test.doc + }; + + final TestContext testContext = new TestContext(); + + final String TEST_ROOT_DIR = "\\ContentDiskDriverTest"; + final String TEST_DIR = "\\ContentDiskDriverTest\\testScenarioEmacsSave"; + + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + final SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + final TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + /** + * Create a file in the test directory + */ + RetryingTransactionCallback createFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + /** + * Create the test directory we are going to use + */ + FileOpenParams createRootDirParams = new FileOpenParams(TEST_ROOT_DIR, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + FileOpenParams createDirParams = new FileOpenParams(TEST_DIR, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + driver.createDirectory(testSession, testConnection, createRootDirParams); + driver.createDirectory(testSession, testConnection, createDirParams); + + /** + * Create the file we are going to use + */ + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NAME, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.firstFileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull(testContext.firstFileHandle); + + // no need to test lots of different properties, that's already been tested above + testContext.testNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + nodeService.setProperty(testContext.testNodeRef, TransferModel.PROP_ENABLED, true); + + return null; + } + }; + tran.doInTransaction(createFileCB); + + /** + * a) Write some content to the test file + */ + RetryingTransactionCallback writeFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + String testContent = "Emacs shuffle test"; + byte[] testContentBytes = testContent.getBytes(); + testContext.firstFileHandle.writeFile(testContentBytes, testContentBytes.length, 0, 0); + testContext.firstFileHandle.close(); + return null; + } + }; + tran.doInTransaction(writeFileCB); + + /** + * b) rename the old file out of the way + */ + RetryingTransactionCallback renameOldFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.renameFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NAME, TEST_DIR + "\\" + FILE_OLD_TEMP); + return null; + } + }; + tran.doInTransaction(renameOldFileCB); + + /** + * c) Save the new file + */ + RetryingTransactionCallback saveNewFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NAME, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.newFileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull(testContext.newFileHandle); + String testContent = "EMACS shuffle test This is new content"; + byte[] testContentBytes = testContent.getBytes(); + testContext.newFileHandle.writeFile(testContentBytes, testContentBytes.length, 0, 0); + testContext.newFileHandle.close(); + + return null; + } + }; + tran.doInTransaction(saveNewFileCB); + + RetryingTransactionCallback validateCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NodeRef shuffledNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + + Map props = nodeService.getProperties(shuffledNodeRef); + assertTrue("node does not contain shuffled ENABLED property", props.containsKey(TransferModel.PROP_ENABLED)); + return null; + } + }; + + tran.doInTransaction(validateCB); + + } // testScenarioEmacs save + + /** + * This test tries to simulate the cifs shuffling that is done to + * support vi + * + * a) viTest.txt + * b) Rename original file to viTest.txt~ + * c) Create viTest.txt + */ + public void DISABLED_testScenarioViSave() throws Exception + { + logger.debug("testScenarioViSave"); + final String FILE_NAME = "viTest.txt"; + final String FILE_OLD_TEMP = "viTest.txt~"; + + class TestContext + { + NetworkFile firstFileHandle; + NetworkFile newFileHandle; + NodeRef testNodeRef; // node ref of test.doc + }; + + final TestContext testContext = new TestContext(); + + final String TEST_ROOT_DIR = "\\ContentDiskDriverTest"; + final String TEST_DIR = "\\ContentDiskDriverTest\\testScenarioViSave"; + + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + final SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + final TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + /** + * Create a file in the test directory + */ + RetryingTransactionCallback createFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + /** + * Create the test directory we are going to use + */ + FileOpenParams createRootDirParams = new FileOpenParams(TEST_ROOT_DIR, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + FileOpenParams createDirParams = new FileOpenParams(TEST_DIR, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + driver.createDirectory(testSession, testConnection, createRootDirParams); + driver.createDirectory(testSession, testConnection, createDirParams); + + /** + * Create the file we are going to use + */ + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NAME, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.firstFileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull(testContext.firstFileHandle); + + // no need to test lots of different properties, that's already been tested above + testContext.testNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + nodeService.setProperty(testContext.testNodeRef, TransferModel.PROP_ENABLED, true); + + return null; + } + }; + tran.doInTransaction(createFileCB); + + /** + * a) Write some content to the test file + */ + RetryingTransactionCallback writeFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + String testContent = "Emacs shuffle test"; + byte[] testContentBytes = testContent.getBytes(); + testContext.firstFileHandle.writeFile(testContentBytes, testContentBytes.length, 0, 0); + testContext.firstFileHandle.close(); + return null; + } + }; + tran.doInTransaction(writeFileCB); + + /** + * b) rename the old file out of the way + */ + RetryingTransactionCallback renameOldFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.renameFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NAME, TEST_DIR + "\\" + FILE_OLD_TEMP); + return null; + } + }; + tran.doInTransaction(renameOldFileCB); + + /** + * c) Save the new file + */ + RetryingTransactionCallback saveNewFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NAME, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.newFileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull(testContext.newFileHandle); + String testContent = "EMACS shuffle test This is new content"; + byte[] testContentBytes = testContent.getBytes(); + testContext.newFileHandle.writeFile(testContentBytes, testContentBytes.length, 0, 0); + testContext.newFileHandle.close(); + + return null; + } + }; + tran.doInTransaction(saveNewFileCB); + + RetryingTransactionCallback validateCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NodeRef shuffledNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + assertNotNull("shuffledNodeRef is null", shuffledNodeRef); + + Map props = nodeService.getProperties(shuffledNodeRef); + assertEquals("name wrong", FILE_NAME, nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_NAME) ); + assertTrue("node does not contain shuffled ENABLED property", props.containsKey(TransferModel.PROP_ENABLED)); + + return null; + } + }; + + tran.doInTransaction(validateCB); + + } // testScenarioViSave + + /** + * This test tries to simulate the cifs shuffling that is done to + * support smultron + * + * a) smultronTest.txt + * b) Save new file to .dat04cd.004 + * c) Delete smultronTest.txt + * c) Rename .dat04cd.004 to smultronTest.txt + */ + public void DISABLED_testScenarioSmultronSave() throws Exception + { + logger.debug("testScenarioSmultronSave"); + final String FILE_NAME = "smultronTest.txt"; + final String FILE_NEW_TEMP = ".dat04cd.004"; + + class TestContext + { + NetworkFile firstFileHandle; + NetworkFile newFileHandle; + NodeRef testNodeRef; // node ref of test.doc + }; + + final TestContext testContext = new TestContext(); + + final String TEST_ROOT_DIR = "\\ContentDiskDriverTest"; + final String TEST_DIR = "\\ContentDiskDriverTest\\testScenarioSmultronSave"; + + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + final SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + final TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + /** + * Create a file in the test directory + */ + RetryingTransactionCallback createFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + /** + * Create the test directory we are going to use + */ + FileOpenParams createRootDirParams = new FileOpenParams(TEST_ROOT_DIR, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + FileOpenParams createDirParams = new FileOpenParams(TEST_DIR, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + driver.createDirectory(testSession, testConnection, createRootDirParams); + driver.createDirectory(testSession, testConnection, createDirParams); + + /** + * Create the file we are going to use + */ + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NAME, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.firstFileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull(testContext.firstFileHandle); + + // no need to test lots of different properties, that's already been tested above + testContext.testNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + nodeService.setProperty(testContext.testNodeRef, TransferModel.PROP_ENABLED, true); + + return null; + } + }; + tran.doInTransaction(createFileCB); + + /** + * a) Write some content to the test file + */ + RetryingTransactionCallback writeFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + String testContent = "Smultron shuffle test"; + byte[] testContentBytes = testContent.getBytes(); + testContext.firstFileHandle.writeFile(testContentBytes, testContentBytes.length, 0, 0); + testContext.firstFileHandle.close(); + return null; + } + }; + tran.doInTransaction(writeFileCB); + + /** + * b) Save the new file + */ + RetryingTransactionCallback saveNewFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NEW_TEMP, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.newFileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull(testContext.newFileHandle); + String testContent = "Smultron shuffle test This is new content"; + byte[] testContentBytes = testContent.getBytes(); + testContext.newFileHandle.writeFile(testContentBytes, testContentBytes.length, 0, 0); + testContext.newFileHandle.close(); + + return null; + } + }; + tran.doInTransaction(saveNewFileCB); + + /** + * c) Delete the old file + */ + RetryingTransactionCallback deleteOldFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.deleteFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NAME); + return null; + } + }; + tran.doInTransaction(deleteOldFileCB); + + /** + * d) Move the new file into place, stuff should get shuffled + */ + RetryingTransactionCallback moveNewFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.renameFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NEW_TEMP, TEST_DIR + "\\" + FILE_NAME); + return null; + } + }; + + tran.doInTransaction(moveNewFileCB); + + RetryingTransactionCallback validateCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NodeRef shuffledNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + + Map props = nodeService.getProperties(shuffledNodeRef); + assertTrue("node does not contain shuffled ENABLED property", props.containsKey(TransferModel.PROP_ENABLED)); + assertEquals("name wrong", FILE_NAME, nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_NAME) ); + return null; + } + }; + + tran.doInTransaction(validateCB); + + } // testScenarioSmultronSave + + + /** + * This time we create a file through the ContentDiskDriver and then delete it + * through the repo. We check its no longer found by the driver. + */ + public void testScenarioDeleteViaNodeService() throws Exception + { + logger.debug("testScenarioDeleteViaNodeService"); + + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + int openAction = FileAction.CreateNotExist; + String FILE_PATH="\\testCreateFile.new"; + + FileOpenParams params = new FileOpenParams(FILE_PATH, openAction, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + + final NetworkFile file = driver.createFile(testSession, testConnection, params); + + assertNotNull("file is null", file); + assertFalse("file is read only, should be read-write", file.isReadOnly()); + + RetryingTransactionCallback writeFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + + byte[] stuff = "Hello World".getBytes(); + file.writeFile(stuff, stuff.length, 0, 0); + file.close(); + + NodeRef companyHome = repositoryHelper.getCompanyHome(); + NodeRef newNode = nodeService.getChildByName(companyHome, ContentModel.ASSOC_CONTAINS, "testCreateFile.new"); + assertNotNull("can't find new node", newNode); + + + return null; + } + }; + tran.doInTransaction(writeFileCB, false, true); + + /** + * Step 1: Delete the new node via the node service + */ + RetryingTransactionCallback deleteNodeCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + + + NodeRef companyHome = repositoryHelper.getCompanyHome(); + NodeRef newNode = nodeService.getChildByName(companyHome, ContentModel.ASSOC_CONTAINS, "testCreateFile.new"); + assertNotNull("can't find new node", newNode); + nodeService.deleteNode(newNode); + return null; + } + }; + tran.doInTransaction(deleteNodeCB, false, true); + + try + { + driver.getNodeForPath(testConnection, FILE_PATH); + fail("getNode for path unexpectedly succeeded"); + } + catch (IOException ie) + { + // expect to go here + } + + /** + * Delete file by path - file should no longer exist + */ + try + { + driver.deleteFile(testSession, testConnection, FILE_PATH); + fail("delete unexpectedly succeeded"); + } + catch (IOException ie) + { + // expect to go here + } + + } + + /** + * This test tries to simulate the shuffling that is done by MS Word 2003 + * with regard to metadata extraction. + *

+ * 1: Setup an inbound rule for ContentMetadataExtractor. + * 2: Write ContentDiskDriverTest1 file to ContentDiskDriver.docx + * 3: Check metadata extraction for non update test + * Simulate a WORD 2003 CIFS shuffle + * 4: Write ContentDiskDriverTest2 file to ~WRD0003.TMP + * 5: Rename ContentDiskDriver.docx to ~WRL0003.TMP + * 6: Rename ~WRD0003.TMP to ContentDiskDriver.docx + * 7: Check metadata extraction + */ + public void testMetadataExtraction() throws Exception + { + logger.debug("testMetadataExtraction"); + final String FILE_NAME = "ContentDiskDriver.docx"; + final String FILE_OLD_TEMP = "~WRL0003.TMP"; + final String FILE_NEW_TEMP = "~WRD0003.TMP"; + + class TestContext + { + NodeRef testDirNodeRef; + NodeRef testNodeRef; + NetworkFile firstFileHandle; + NetworkFile secondFileHandle; + }; + + final TestContext testContext = new TestContext(); + + final String TEST_DIR = TEST_ROOT_DOS_PATH + "\\testMetadataExtraction"; + + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + final SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + final TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + /** + * Clean up just in case garbage is left from a previous run + */ + RetryingTransactionCallback deleteGarbageDirCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.deleteDirectory(testSession, testConnection, TEST_DIR); + return null; + } + }; + + try + { + tran.doInTransaction(deleteGarbageDirCB); + } + catch (Exception e) + { + // expect to go here + } + + logger.debug("create Test directory" + TEST_DIR); + RetryingTransactionCallback createTestDirCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + /** + * Create the test directory we are going to use + */ + FileOpenParams createRootDirParams = new FileOpenParams(TEST_ROOT_DOS_PATH, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + FileOpenParams createDirParams = new FileOpenParams(TEST_DIR, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + driver.createDirectory(testSession, testConnection, createRootDirParams); + driver.createDirectory(testSession, testConnection, createDirParams); + + testContext.testDirNodeRef = driver.getNodeForPath(testConnection, TEST_DIR); + assertNotNull("testDirNodeRef is null", testContext.testDirNodeRef); + + UserTransaction txn = transactionService.getUserTransaction(); + + return null; + + + } + }; + tran.doInTransaction(createTestDirCB); + logger.debug("Create rule on test dir"); + + RetryingTransactionCallback createRuleCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + Rule rule = new Rule(); + rule.setRuleType(RuleType.INBOUND); + rule.applyToChildren(true); + rule.setRuleDisabled(false); + rule.setTitle("Extract Metadata from content"); + rule.setDescription("ContentDiskDriverTest"); + + Map props = new HashMap(1); + Action extractAction = actionService.createAction("extract-metadata", props); + + ActionCondition noCondition1 = actionService.createActionCondition(NoConditionEvaluator.NAME); + extractAction.addActionCondition(noCondition1); + + ActionCondition noCondition2 = actionService.createActionCondition(NoConditionEvaluator.NAME); + CompositeAction compAction = actionService.createCompositeAction(); + compAction.setTitle("Extract Metadata"); + compAction.setDescription("Content Disk Driver Test - Extract Metadata"); + compAction.addAction(extractAction); + compAction.addActionCondition(noCondition2); + + rule.setAction(compAction); + + ruleService.saveRule(testContext.testDirNodeRef, rule); + + logger.debug("rule created"); + + return null; + } + }; + tran.doInTransaction(createRuleCB, false, true); + + /** + * Create a file in the test directory + */ + logger.debug("create test file in test directory"); + RetryingTransactionCallback createFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + /** + * Create the file we are going to use to test + */ + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NAME, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.firstFileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull(testContext.firstFileHandle); + + // now load up the node with lots of other stuff that we will test to see if it gets preserved during the + // shuffle. + testContext.testNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + assertNotNull("testContext.testNodeRef is null", testContext.testNodeRef); + + // test non CM namespace property + nodeService.setProperty(testContext.testNodeRef, TransferModel.PROP_ENABLED, true); + + return null; + } + }; + tran.doInTransaction(createFileCB, false, true); + + logger.debug("step b: write content to test file"); + + /** + * Write ContentDiskDriverTest1.docx to the test file, + */ + RetryingTransactionCallback writeFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + ClassPathResource fileResource = new ClassPathResource("filesys/ContentDiskDriverTest1.docx"); + assertNotNull("unable to find test resource filesys/ContentDiskDriverTest1.docx", fileResource); + + byte[] buffer= new byte[1000]; + InputStream is = fileResource.getInputStream(); + try + { + long offset = 0; + int i = is.read(buffer, 0, buffer.length); + while(i > 0) + { + testContext.firstFileHandle.writeFile(buffer, i, 0, offset); + offset += i; + i = is.read(buffer, 0, buffer.length); + } + } + finally + { + is.close(); + } + + testContext.firstFileHandle.close(); + + return null; + } + }; + tran.doInTransaction(writeFileCB, false, true); + + logger.debug("Step c: validate metadata has been extracted."); + + /** + * c: check simple case of meta-data extraction has worked. + */ + RetryingTransactionCallback validateFirstExtractionCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + Map props = nodeService.getProperties(testContext.testNodeRef); + + assertTrue("Enabled property has been lost", props.containsKey(TransferModel.PROP_ENABLED)); + + // These metadata values should be extracted. + assertEquals("description is not correct", "This is a test file", nodeService.getProperty(testContext.testNodeRef, ContentModel.PROP_DESCRIPTION)); + assertEquals("title is not correct", "ContentDiskDriverTest", nodeService.getProperty(testContext.testNodeRef, ContentModel.PROP_TITLE)); + assertEquals("author is not correct", "mrogers", nodeService.getProperty(testContext.testNodeRef, ContentModel.PROP_AUTHOR)); + + ContentData data = (ContentData)props.get(ContentModel.PROP_CONTENT); + assertEquals("mimeType is wrong", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", data.getMimetype()); + assertEquals("size is wrong", 11302, data.getSize()); + + return null; + } + }; + tran.doInTransaction(validateFirstExtractionCB, false, true); + + + /** + * d: Save the new file as an update file in the test directory + */ + logger.debug("Step d: create update file in test directory " + FILE_NEW_TEMP); + RetryingTransactionCallback createUpdateFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + /** + * Create the file we are going to use to test + */ + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NEW_TEMP, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.secondFileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull(testContext.secondFileHandle); + + return null; + } + }; + tran.doInTransaction(createUpdateFileCB, false, true); + + RetryingTransactionCallback writeFile2CB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + ClassPathResource fileResource = new ClassPathResource("filesys/ContentDiskDriverTest2.docx"); + assertNotNull("unable to find test resource filesys/ContentDiskDriverTest2.docx", fileResource); + + byte[] buffer= new byte[1000]; + InputStream is = fileResource.getInputStream(); + try + { + long offset = 0; + int i = is.read(buffer, 0, buffer.length); + while(i > 0) + { + testContext.secondFileHandle.writeFile(buffer, i, 0, offset); + offset += i; + i = is.read(buffer, 0, buffer.length); + } + } + finally + { + is.close(); + } + + testContext.secondFileHandle.close(); + + return null; + } + }; + tran.doInTransaction(writeFile2CB, false, true); + + /** + * rename the old file + */ + logger.debug("move old file out of the way."); + RetryingTransactionCallback renameOldFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.renameFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NAME, TEST_DIR + "\\" + FILE_OLD_TEMP); + return null; + } + }; + tran.doInTransaction(renameOldFileCB, false, true); + + /** + * Check the old file has gone. + */ + RetryingTransactionCallback validateOldFileGoneCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + try + { + driver.deleteFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NAME); + } + catch (IOException e) + { + // expect to go here since previous step renamed the file. + } + + return null; + } + }; + tran.doInTransaction(validateOldFileGoneCB, false, true); + +// /** +// * Check metadata extraction on intermediate new file +// */ +// RetryingTransactionCallback validateIntermediateCB = new RetryingTransactionCallback() { +// +// @Override +// public Void execute() throws Throwable +// { +// NodeRef updateNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NEW_TEMP); +// +// Map props = nodeService.getProperties(updateNodeRef); +// +// // These metadata values should be extracted from file2. +// assertEquals("intermediate file description is not correct", "Content Disk Test 2", props.get(ContentModel.PROP_DESCRIPTION)); +// assertEquals("intermediate file title is not correct", "Updated", props.get(ContentModel.PROP_TITLE)); +// assertEquals("intermediate file author is not correct", "mrogers", props.get(ContentModel.PROP_AUTHOR)); +// +// return null; +// } +// }; +// +// tran.doInTransaction(validateIntermediateCB, true, true); + + /** + * Move the new file into place, stuff should get shuffled + */ + logger.debug("move new file into place."); + RetryingTransactionCallback moveNewFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.renameFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NEW_TEMP, TEST_DIR + "\\" + FILE_NAME); + return null; + } + }; + + tran.doInTransaction(moveNewFileCB, false, true); + + logger.debug("validate update has run correctly."); + RetryingTransactionCallback validateUpdateCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NodeRef shuffledNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + + Map props = nodeService.getProperties(shuffledNodeRef); + + // Check trx:enabled has been shuffled and not lost. + assertTrue("node does not contain shuffled ENABLED property", props.containsKey(TransferModel.PROP_ENABLED)); + + ContentData data = (ContentData)props.get(ContentModel.PROP_CONTENT); + assertEquals("mimeType is wrong", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", data.getMimetype()); + assertEquals("size is wrong", 11265, data.getSize()); + + // These metadata values should be extracted from file2. However they will not be applied in PRAGMATIC mode. +// assertEquals("description is not correct", "Content Disk Test 2", props.get(ContentModel.PROP_DESCRIPTION)); +// assertEquals("title is not correct", "Updated", props.get(ContentModel.PROP_TITLE)); +// assertEquals("author is not correct", "mrogers", props.get(ContentModel.PROP_AUTHOR)); + + return null; + } + }; + + tran.doInTransaction(validateUpdateCB, true, true); + + } // testScenarioShuffleMetadataExtraction + + public void testDirListing()throws Exception + { + logger.debug("testDirListing"); + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + final String FOLDER_NAME = "parentFolder" + System.currentTimeMillis(); + final String HIDDEN_FOLDER_NAME = "hiddenFolder" + System.currentTimeMillis(); + RetryingTransactionCallback createNodesCB = new RetryingTransactionCallback() { + + @Override + public NodeRef execute() throws Throwable + { + NodeRef companyHome = repositoryHelper.getCompanyHome(); + NodeRef parentNode = nodeService.createNode(companyHome, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, FOLDER_NAME), ContentModel.TYPE_FOLDER).getChildRef(); + nodeService.setProperty(parentNode, ContentModel.PROP_NAME, FOLDER_NAME); + + NodeRef hiddenNode = nodeService.createNode(parentNode, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, HIDDEN_FOLDER_NAME), ForumModel.TYPE_FORUM).getChildRef(); + nodeService.setProperty(hiddenNode, ContentModel.PROP_NAME, HIDDEN_FOLDER_NAME); + return parentNode; + } + }; + final NodeRef parentFolder = tran.doInTransaction(createNodesCB); + + List excludedTypes = new ArrayList(); + excludedTypes.add(ForumModel.TYPE_FORUM.toString()); + cifsHelper.setExcludedTypes(excludedTypes); + SearchContext result = driver.startSearch(testSession, testConnection, "\\"+FOLDER_NAME + "\\*", 0); + while(result.hasMoreFiles()) + { + if (result.nextFileName().equals(HIDDEN_FOLDER_NAME)) + { + fail("Exluded types mustn't be shown in cifs"); + } + } + + RetryingTransactionCallback deleteNodeCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + nodeService.deleteNode(parentFolder); + return null; + } + }; + tran.doInTransaction(deleteNodeCB, false, true); + } //testDirListing + + /** + * Test server + */ + public class TestServer extends NetworkFileServer + { + + public TestServer(String proto, ServerConfiguration config) + { + super(proto, config); + // TODO Auto-generated constructor stub + } + + @Override + public void startServer() + { + + } + + @Override + public void shutdownServer(boolean immediate) + { + + } + + public TreeConnection getTreeConnection(SharedDevice share) + { + return new TreeConnection(share); + } + } + + /** + * TestSrvSession + */ + private class TestSrvSession extends SrvSession + { + + public TestSrvSession(int sessId, NetworkServer srv, String proto, + String remName) + { + super(sessId, srv, proto, remName); + } + + @Override + public InetAddress getRemoteAddress() + { + return null; + } + + @Override + public boolean useCaseSensitiveSearch() + { + return false; + } + } +} diff --git a/source/java/org/alfresco/filesys/repo/ContentQuotaManager.java b/source/java/org/alfresco/filesys/repo/ContentQuotaManager.java index bb71ab590a..d726d48937 100644 --- a/source/java/org/alfresco/filesys/repo/ContentQuotaManager.java +++ b/source/java/org/alfresco/filesys/repo/ContentQuotaManager.java @@ -40,7 +40,9 @@ import org.apache.commons.logging.LogFactory; /** * Content Quota Manager Class * - *

Quota manager implementation for the Alfresco repository. + *

Implementation of JLAN QuotaManager interface for the Alfresco repository. + *

Keeps an in memory quota for each active user. After a configurable length of + * time quotas are removed from memory. * * @author gkspencer * diff --git a/source/java/org/alfresco/filesys/repo/ContentSearchContext.java b/source/java/org/alfresco/filesys/repo/ContentSearchContext.java index e33a37e5ed..ec1862a666 100644 --- a/source/java/org/alfresco/filesys/repo/ContentSearchContext.java +++ b/source/java/org/alfresco/filesys/repo/ContentSearchContext.java @@ -134,7 +134,7 @@ public class ContentSearchContext extends SearchContext */ public int getResumeId() { - return resumeId - 1; + return resumeId; } /** @@ -467,6 +467,10 @@ public class ContentSearchContext extends SearchContext */ public boolean restartAt(int resumeId) { + // Resume ids are one based as zero has special meaning for some protocols, adjust the resume id + + resumeId--; + // Check if the resume point is in the pseudo file list if (pseudoList != null) diff --git a/source/java/org/alfresco/filesys/repo/desk/JavaScriptDesktopAction.java b/source/java/org/alfresco/filesys/repo/desk/JavaScriptDesktopAction.java index dd7a1b6d44..93499363de 100644 --- a/source/java/org/alfresco/filesys/repo/desk/JavaScriptDesktopAction.java +++ b/source/java/org/alfresco/filesys/repo/desk/JavaScriptDesktopAction.java @@ -19,9 +19,8 @@ package org.alfresco.filesys.repo.desk; import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; import java.io.IOException; +import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; import java.util.StringTokenizer; @@ -34,7 +33,6 @@ import org.alfresco.filesys.alfresco.DesktopParams; import org.alfresco.filesys.alfresco.DesktopResponse; import org.alfresco.filesys.alfresco.AlfrescoDiskDriver.CallableIO; import org.alfresco.jlan.server.filesys.DiskSharedDevice; -import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.scripts.ScriptException; import org.alfresco.service.cmr.repository.ScriptService; import org.alfresco.util.ResourceFinder; @@ -56,8 +54,8 @@ public class JavaScriptDesktopAction extends DesktopAction { // Script file details - private String m_scriptPath; - private long m_lastModified; + private Resource m_scriptResource; + private Long m_lastModified; // Script string @@ -147,34 +145,27 @@ public class JavaScriptDesktopAction extends DesktopAction { } // Check if the script exists on the classpath - Resource resource = new ResourceFinder().getResource(m_scriptName); - if (!resource.exists()) + m_scriptResource = new ResourceFinder().getResource(m_scriptName); + if (!m_scriptResource.exists()) { throw new DesktopActionException("Failed to find script on classpath, " + getScriptName()); } - - // Check that the script file exists - File scriptFile; + // Get the script modification date if it can be resolved to a file try { - scriptFile = resource.getFile(); - if (scriptFile.exists() == false) - throw new DesktopActionException("Script file not found, " + m_scriptName); + m_lastModified = m_scriptResource.lastModified(); } catch (IOException e) { - throw new DesktopActionException("Unable to resolve script as a file, " + resource.getDescription()); + // Don't worry if we can't. Assume it's embedded in a resource. } - m_scriptPath = scriptFile.getAbsolutePath(); - m_lastModified =scriptFile.lastModified(); - // Load the script try { - loadScript( scriptFile); + loadScript( m_scriptResource); } catch ( IOException ex) { @@ -192,26 +183,24 @@ public class JavaScriptDesktopAction extends DesktopAction { public DesktopResponse runAction(DesktopParams params) throws DesktopActionException { - File scriptFile = new File(m_scriptPath); - synchronized (this) + synchronized (this) { - if (scriptFile.lastModified() != m_lastModified) + try { - // Reload the script - - m_lastModified = scriptFile.lastModified(); - - try + if (m_lastModified != null && m_scriptResource.lastModified() != m_lastModified) { - loadScript(scriptFile); - } - catch (IOException ex) - { - // Check if the script file has been changed - return new DesktopResponse(StsError, "Failed to reload script file, " + getScriptName()); + // Reload the script if we can + + m_lastModified = m_scriptResource.lastModified(); + + loadScript(m_scriptResource); } } + catch (IOException ex) + { + logger.warn("Failed to reload script file, " + m_scriptResource.getDescription(), ex); + } } // Access the script service @@ -444,29 +433,29 @@ public class JavaScriptDesktopAction extends DesktopAction { * * @param scriptFile File */ - private final void loadScript(File scriptFile) - throws IOException - { - // Open the script file - - BufferedReader scriptIn = new BufferedReader(new FileReader( scriptFile)); - StringBuilder scriptStr = new StringBuilder((int) scriptFile.length() + 256); - - String inRec = scriptIn.readLine(); - - while ( inRec != null) - { - scriptStr.append( inRec); - scriptStr.append( "\n"); - inRec = scriptIn.readLine(); - } - - // Close the script file - - scriptIn.close(); - - // Update the script string - - m_script = scriptStr.toString(); - } + private final void loadScript(Resource scriptResource) + throws IOException + { + // Get resource + BufferedReader scriptIn = new BufferedReader(new InputStreamReader(scriptResource.getInputStream())); + StringBuilder scriptStr = new StringBuilder(1024); + try + { + String inRec = scriptIn.readLine(); + while ( inRec != null) + { + scriptStr.append( inRec); + scriptStr.append( "\n"); + inRec = scriptIn.readLine(); + } + } + finally + { + // Close the script file + scriptIn.close(); + } + // Update the script string + m_script = scriptStr.toString(); + } + } diff --git a/source/java/org/alfresco/filesys/repo/package-info.java b/source/java/org/alfresco/filesys/repo/package-info.java new file mode 100644 index 0000000000..3ca1b96388 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/package-info.java @@ -0,0 +1,19 @@ +/** + * The Alfesco filesystem to repository translation layer + * + *

+ * Alfresco ContentDiskDriver and supporting classes. + * + *

+ * NodeEventQueue containing NodeEvents such as when a node + * is created or deleted. + * + *

+ * NodeMonitor which is bound to various node policies and updates + * the file state cache. + * + *

+ * Quota Management which contains a UserQuota for each active user. + * + */ +package org.alfresco.filesys.repo; diff --git a/source/java/org/alfresco/jcr/exporter/JCRDocumentXMLExporter.java b/source/java/org/alfresco/jcr/exporter/JCRDocumentXMLExporter.java index 8f1952a484..ee1f161dc8 100644 --- a/source/java/org/alfresco/jcr/exporter/JCRDocumentXMLExporter.java +++ b/source/java/org/alfresco/jcr/exporter/JCRDocumentXMLExporter.java @@ -297,8 +297,11 @@ public class JCRDocumentXMLExporter implements Exporter */ public void value(NodeRef nodeRef, QName property, Object value, int index) { - currentProperties.add(property); - currentValues.add(value); + if (value != null) + { + currentProperties.add(property); + currentValues.add(value); + } } /* (non-Javadoc) diff --git a/source/java/org/alfresco/jcr/exporter/JCRSystemXMLExporter.java b/source/java/org/alfresco/jcr/exporter/JCRSystemXMLExporter.java index 511e1a3e99..8640e86338 100644 --- a/source/java/org/alfresco/jcr/exporter/JCRSystemXMLExporter.java +++ b/source/java/org/alfresco/jcr/exporter/JCRSystemXMLExporter.java @@ -340,11 +340,14 @@ public class JCRSystemXMLExporter implements Exporter { try { - // emit value element - contentHandler.startElement(VALUE_QNAME.getNamespaceURI(), VALUE_LOCALNAME, toPrefixString(VALUE_QNAME), EMPTY_ATTRIBUTES); - String strValue = session.getTypeConverter().convert(String.class, value); - contentHandler.characters(strValue.toCharArray(), 0, strValue.length()); - contentHandler.endElement(VALUE_QNAME.getNamespaceURI(), VALUE_LOCALNAME, toPrefixString(VALUE_QNAME)); + if (value != null) + { + // emit value element + contentHandler.startElement(VALUE_QNAME.getNamespaceURI(), VALUE_LOCALNAME, toPrefixString(VALUE_QNAME), EMPTY_ATTRIBUTES); + String strValue = session.getTypeConverter().convert(String.class, value); + contentHandler.characters(strValue.toCharArray(), 0, strValue.length()); + contentHandler.endElement(VALUE_QNAME.getNamespaceURI(), VALUE_LOCALNAME, toPrefixString(VALUE_QNAME)); + } } catch (RepositoryException e) { diff --git a/source/java/org/alfresco/jcr/item/ItemTest.java b/source/java/org/alfresco/jcr/item/ItemTest.java index 5d0038c5ba..98b52a7fed 100644 --- a/source/java/org/alfresco/jcr/item/ItemTest.java +++ b/source/java/org/alfresco/jcr/item/ItemTest.java @@ -186,7 +186,7 @@ public class ItemTest extends BaseJCRTest assertEquals(i+1, vi.getSize()); Version v = content.getBaseVersion(); - assertEquals("1."+i, v.getName()); + assertEquals("", "0."+ (i + 1), v.getName()); org.alfresco.service.cmr.version.VersionHistory versionHistory = versionService.getVersionHistory(nodeRef); @@ -197,7 +197,7 @@ public class ItemTest extends BaseJCRTest long beforeDuration = System.currentTimeMillis() - startTime; - assertEquals("1."+i, version.getVersionLabel()); + assertEquals("0."+(i + 1), version.getVersionLabel()); // After @@ -211,7 +211,7 @@ public class ItemTest extends BaseJCRTest long afterDuration = System.currentTimeMillis() - startTime; - assertEquals("1."+i, version.getVersionLabel()); + assertEquals("0."+(i + 1), version.getVersionLabel()); System.out.println("getBaseVersion - get current version (BEFORE: " + beforeDuration + "ms, AFTER: " + afterDuration + "ms) "); } diff --git a/source/java/org/alfresco/repo/action/ActionServiceImpl.java b/source/java/org/alfresco/repo/action/ActionServiceImpl.java index 09817aee1d..682d214331 100644 --- a/source/java/org/alfresco/repo/action/ActionServiceImpl.java +++ b/source/java/org/alfresco/repo/action/ActionServiceImpl.java @@ -682,7 +682,7 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A if (logger.isDebugEnabled() == true) { - logger.debug("Adding " + action.getId() + " to action chain."); + logger.debug("Adding " + action.getActionDefinitionName() + ", " + action.getId() + " to action chain."); } try @@ -1587,7 +1587,11 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A if (pendingActions.contains(pendingAction) == false) { pendingActions.add(pendingAction); - actionTrackingService.recordActionPending(action); + + if (getTrackStatus(action)) + { + actionTrackingService.recordActionPending(action); + } } } } diff --git a/source/java/org/alfresco/repo/action/ActionServiceImplTest.java b/source/java/org/alfresco/repo/action/ActionServiceImplTest.java index 9ce27872da..2944d52ab5 100644 --- a/source/java/org/alfresco/repo/action/ActionServiceImplTest.java +++ b/source/java/org/alfresco/repo/action/ActionServiceImplTest.java @@ -1267,8 +1267,7 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest protected Action createFailingMoveAction() { Action failingAction = this.actionService.createAction(MoveActionExecuter.NAME); - failingAction.setParameterValue(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CHILDREN); - failingAction.setParameterValue(MoveActionExecuter.PARAM_ASSOC_QNAME, ContentModel.ASSOC_CHILDREN); + // Create a bad node ref NodeRef badNodeRef = new NodeRef(this.storeRef, "123123"); failingAction.setParameterValue(MoveActionExecuter.PARAM_DESTINATION_FOLDER, badNodeRef); diff --git a/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java b/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java index 444162689b..4b2a207dba 100644 --- a/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java +++ b/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java @@ -1229,12 +1229,7 @@ public class ActionTrackingServiceImplTest extends TestCase { Action failingAction = this.actionService.createAction(MoveActionExecuter.NAME); failingAction.setTrackStatus(Boolean.TRUE); - failingAction.setParameterValue( - MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, - ContentModel.ASSOC_CHILDREN); - failingAction.setParameterValue( - MoveActionExecuter.PARAM_ASSOC_QNAME, - ContentModel.ASSOC_CHILDREN); + // Create a bad node ref NodeRef badNodeRef = new NodeRef(this.storeRef, "123123"); failingAction.setParameterValue(MoveActionExecuter.PARAM_DESTINATION_FOLDER, badNodeRef); diff --git a/source/java/org/alfresco/repo/action/executer/ContentMetadataExtracter.java b/source/java/org/alfresco/repo/action/executer/ContentMetadataExtracter.java index f600d02d86..12910d11f8 100644 --- a/source/java/org/alfresco/repo/action/executer/ContentMetadataExtracter.java +++ b/source/java/org/alfresco/repo/action/executer/ContentMetadataExtracter.java @@ -124,6 +124,10 @@ public class ContentMetadataExtracter extends ActionExecuterAbstractBase // The reader may be null, e.g. for folders and the like if (reader == null || reader.getMimetype() == null) { + if(logger.isDebugEnabled()) + { + logger.debug("no content or mimetype - do nothing"); + } // No content to extract data from return; } @@ -131,6 +135,10 @@ public class ContentMetadataExtracter extends ActionExecuterAbstractBase MetadataExtracter extracter = metadataExtracterRegistry.getExtracter(mimetype); if (extracter == null) { + if(logger.isDebugEnabled()) + { + logger.debug("no extracter for mimetype:" + mimetype); + } // There is no extracter to use return; } diff --git a/source/java/org/alfresco/repo/action/executer/CopyActionExecuter.java b/source/java/org/alfresco/repo/action/executer/CopyActionExecuter.java index cd887f98b0..a661332ad0 100644 --- a/source/java/org/alfresco/repo/action/executer/CopyActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/CopyActionExecuter.java @@ -28,11 +28,11 @@ import org.alfresco.repo.action.ParameterDefinitionImpl; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ParameterDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.CopyService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.rule.RuleServiceException; -import org.alfresco.service.namespace.QName; /** * Copy action executor. @@ -47,8 +47,6 @@ public class CopyActionExecuter extends ActionExecuterAbstractBase public static final String NAME = "copy"; public static final String PARAM_DESTINATION_FOLDER = "destination-folder"; - public static final String PARAM_ASSOC_TYPE_QNAME = "assoc-type"; - public static final String PARAM_ASSOC_QNAME = "assoc-name"; public static final String PARAM_DEEP_COPY = "deep-copy"; public static final String PARAM_OVERWRITE_COPY = "overwrite-copy"; @@ -90,8 +88,6 @@ public class CopyActionExecuter extends ActionExecuterAbstractBase protected void addParameterDefinitions(List paramList) { paramList.add(new ParameterDefinitionImpl(PARAM_DESTINATION_FOLDER, DataTypeDefinition.NODE_REF, true, getParamDisplayLabel(PARAM_DESTINATION_FOLDER))); - paramList.add(new ParameterDefinitionImpl(PARAM_ASSOC_TYPE_QNAME, DataTypeDefinition.QNAME, true, getParamDisplayLabel(PARAM_ASSOC_TYPE_QNAME))); - paramList.add(new ParameterDefinitionImpl(PARAM_ASSOC_QNAME, DataTypeDefinition.QNAME, false, getParamDisplayLabel(PARAM_ASSOC_QNAME))); paramList.add(new ParameterDefinitionImpl(PARAM_DEEP_COPY, DataTypeDefinition.BOOLEAN, false, getParamDisplayLabel(PARAM_DEEP_COPY))); paramList.add(new ParameterDefinitionImpl(PARAM_OVERWRITE_COPY, DataTypeDefinition.BOOLEAN, false, getParamDisplayLabel(PARAM_OVERWRITE_COPY))); } @@ -101,14 +97,12 @@ public class CopyActionExecuter extends ActionExecuterAbstractBase */ public void executeImpl(Action ruleAction, NodeRef actionedUponNodeRef) { - if (this.nodeService.exists(actionedUponNodeRef) == true) - { - NodeRef destinationParent = (NodeRef)ruleAction.getParameterValue(PARAM_DESTINATION_FOLDER); - QName destinationAssocTypeQName = (QName)ruleAction.getParameterValue(PARAM_ASSOC_TYPE_QNAME); - QName destinationAssocQName = (QName)ruleAction.getParameterValue(PARAM_ASSOC_QNAME); - - // Get the deep copy value - boolean deepCopy = false; + if (this.nodeService.exists(actionedUponNodeRef) == true) + { + NodeRef destinationParent = (NodeRef)ruleAction.getParameterValue(PARAM_DESTINATION_FOLDER); + + // Get the deep copy value + boolean deepCopy = false; Boolean deepCopyValue = (Boolean)ruleAction.getParameterValue(PARAM_DEEP_COPY); if (deepCopyValue != null) { @@ -163,12 +157,13 @@ public class CopyActionExecuter extends ActionExecuterAbstractBase } else { + ChildAssociationRef originalAssoc = nodeService.getPrimaryParent(actionedUponNodeRef); // Create a new copy of the node this.copyService.copyAndRename( actionedUponNodeRef, destinationParent, - destinationAssocTypeQName, - destinationAssocQName, + originalAssoc.getTypeQName(), + originalAssoc.getQName(), deepCopy); } } diff --git a/source/java/org/alfresco/repo/action/executer/ImporterActionExecuter.java b/source/java/org/alfresco/repo/action/executer/ImporterActionExecuter.java index 898ce4356f..24aa910af5 100644 --- a/source/java/org/alfresco/repo/action/executer/ImporterActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/ImporterActionExecuter.java @@ -56,8 +56,8 @@ import org.alfresco.service.cmr.view.ImporterService; import org.alfresco.service.cmr.view.Location; import org.alfresco.service.namespace.QName; import org.alfresco.util.TempFileProvider; -import org.apache.tools.zip.ZipEntry; -import org.apache.tools.zip.ZipFile; +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipFile; /** * Importer action executor @@ -198,10 +198,12 @@ public class ImporterActionExecuter extends ActionExecuterAbstractBase reader.getContent(tempFile); // NOTE: This encoding allows us to workaround bug: // http://bugs.sun.com/bugdatabase/view_bug.do;:WuuT?bug_id=4820807 - zipFile = new ZipFile(tempFile, "Cp437"); + // We also try to use the extra encoding information if present + zipFile = new ZipFile(tempFile, "Cp437", true); // build a temp dir name based on the ID of the noderef we are importing - File alfTempDir = TempFileProvider.getTempDir(); + // also use the long life temp folder as large ZIP files can take a while + File alfTempDir = TempFileProvider.getLongLifeTempDir("import"); File tempDir = new File(alfTempDir.getPath() + File.separatorChar + actionedUponNodeRef.getId()); try { @@ -316,7 +318,7 @@ public class ImporterActionExecuter extends ActionExecuterAbstractBase { for (Enumeration e = archive.getEntries(); e.hasMoreElements();) { - ZipEntry entry = (ZipEntry) e.nextElement(); + ZipArchiveEntry entry = (ZipArchiveEntry) e.nextElement(); if (!entry.isDirectory()) { fileName = entry.getName(); @@ -367,12 +369,24 @@ public class ImporterActionExecuter extends ActionExecuterAbstractBase */ public static void deleteDir(File dir) { - File elenco = new File(dir.getPath()); - for (File file : elenco.listFiles()) + if (dir != null) { - if (file.isFile()) file.delete(); - else deleteDir(file); + File elenco = new File(dir.getPath()); + + // listFiles can return null if the path is invalid i.e. already been deleted, + // therefore check for null before using in loop + File[] files = elenco.listFiles(); + if (files != null) + { + for (File file : files) + { + if (file.isFile()) file.delete(); + else deleteDir(file); + } + } + + // delete provided directory + dir.delete(); } - dir.delete(); } } diff --git a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java index 7d3b945c34..eb1c1b4460 100644 --- a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java @@ -50,9 +50,10 @@ import org.alfresco.service.cmr.security.PersonService; import org.alfresco.util.UrlUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.commons.validator.EmailValidator; +import org.apache.commons.validator.routines.EmailValidator; import org.springframework.beans.factory.InitializingBean; import org.springframework.mail.MailException; +import org.springframework.mail.MailPreparationException; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.mail.javamail.MimeMessagePreparator; @@ -357,10 +358,19 @@ public class MailActionExecuter extends ActionExecuterAbstractBase } } } - else if (authType.equals(AuthorityType.GROUP)) + else if (authType.equals(AuthorityType.GROUP) || authType.equals(AuthorityType.EVERYONE)) { - // else notify all members of the group - Set users = authorityService.getContainedAuthorities(AuthorityType.USER, authority, false); + // Notify all members of the group + Set users; + if (authType.equals(AuthorityType.GROUP)) + { + users = authorityService.getContainedAuthorities(AuthorityType.USER, authority, false); + } + else + { + users = authorityService.getAllAuthorities(AuthorityType.USER); + } + for (String userAuth : users) { if (personService.personExists(userAuth) == true) @@ -376,12 +386,24 @@ public class MailActionExecuter extends ActionExecuterAbstractBase } } - message.setTo(recipients.toArray(new String[recipients.size()])); + if(recipients.size() > 0) + { + message.setTo(recipients.toArray(new String[recipients.size()])); + } + else + { + // All recipients were invalid + throw new MailPreparationException( + "All recipients for the mail action were invalid" + ); + } } else { - // No recipiants have been specified - logger.error("No recipiant has been specified for the mail action"); + // No recipients have been specified + throw new MailPreparationException( + "No recipient has been specified for the mail action" + ); } } diff --git a/source/java/org/alfresco/repo/action/executer/MoveActionExecuter.java b/source/java/org/alfresco/repo/action/executer/MoveActionExecuter.java index f86e78beff..79d2333ec2 100644 --- a/source/java/org/alfresco/repo/action/executer/MoveActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/MoveActionExecuter.java @@ -24,9 +24,9 @@ import org.alfresco.repo.action.ParameterDefinitionImpl; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ParameterDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileNotFoundException; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.namespace.QName; /** * Copy action executor. @@ -39,44 +39,37 @@ public class MoveActionExecuter extends ActionExecuterAbstractBase { public static final String NAME = "move"; public static final String PARAM_DESTINATION_FOLDER = "destination-folder"; - public static final String PARAM_ASSOC_TYPE_QNAME = "assoc-type"; - public static final String PARAM_ASSOC_QNAME = "assoc-name"; /** - * Node service + * FileFolder service */ - private NodeService nodeService; + private FileFolderService fileFolderService; - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } - @Override - protected void addParameterDefinitions(List paramList) - { - paramList.add(new ParameterDefinitionImpl(PARAM_DESTINATION_FOLDER, DataTypeDefinition.NODE_REF, true, getParamDisplayLabel(PARAM_DESTINATION_FOLDER))); - paramList.add(new ParameterDefinitionImpl(PARAM_ASSOC_TYPE_QNAME, DataTypeDefinition.QNAME, true, getParamDisplayLabel(PARAM_ASSOC_TYPE_QNAME))); - paramList.add(new ParameterDefinitionImpl(PARAM_ASSOC_QNAME, DataTypeDefinition.QNAME, true, getParamDisplayLabel(PARAM_ASSOC_QNAME))); - } + @Override + protected void addParameterDefinitions(List paramList) + { + paramList.add(new ParameterDefinitionImpl(PARAM_DESTINATION_FOLDER, DataTypeDefinition.NODE_REF, true, getParamDisplayLabel(PARAM_DESTINATION_FOLDER))); + } /** * @see org.alfresco.repo.action.executer.ActionExecuter#execute(org.alfresco.repo.ref.NodeRef, org.alfresco.repo.ref.NodeRef) */ public void executeImpl(Action ruleAction, NodeRef actionedUponNodeRef) { - if (this.nodeService.exists(actionedUponNodeRef) == true) - { - NodeRef destinationParent = (NodeRef)ruleAction.getParameterValue(PARAM_DESTINATION_FOLDER); - QName destinationAssocTypeQName = (QName)ruleAction.getParameterValue(PARAM_ASSOC_TYPE_QNAME); - QName destinationAssocQName = (QName)ruleAction.getParameterValue(PARAM_ASSOC_QNAME); - - this.nodeService.moveNode( - actionedUponNodeRef, - destinationParent, - destinationAssocTypeQName, - destinationAssocQName); - } + NodeRef destinationParent = (NodeRef)ruleAction.getParameterValue(PARAM_DESTINATION_FOLDER); + try + { + fileFolderService.move(actionedUponNodeRef, destinationParent, null); + } + catch (FileNotFoundException e) + { + // Do nothing + } } } diff --git a/source/java/org/alfresco/repo/activities/feed/AbstractFeedGenerator.java b/source/java/org/alfresco/repo/activities/feed/AbstractFeedGenerator.java index cb986d412e..98321fa01c 100644 --- a/source/java/org/alfresco/repo/activities/feed/AbstractFeedGenerator.java +++ b/source/java/org/alfresco/repo/activities/feed/AbstractFeedGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -20,7 +20,12 @@ package org.alfresco.repo.activities.feed; import org.alfresco.repo.activities.ActivityPostServiceImpl; import org.alfresco.repo.domain.activities.ActivityPostDAO; +import org.alfresco.repo.lock.JobLockService; +import org.alfresco.repo.lock.LockAcquisitionException; +import org.alfresco.repo.lock.JobLockService.JobLockRefreshCallback; import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; import org.alfresco.util.PropertyCheck; import org.alfresco.util.VmShutdownListener; import org.apache.commons.logging.Log; @@ -34,6 +39,12 @@ public abstract class AbstractFeedGenerator implements FeedGenerator { private static Log logger = LogFactory.getLog(AbstractFeedGenerator.class); + /** The name of the lock used to ensure that feed generator does not run on more than one node at the same time */ + private static final QName LOCK_QNAME = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "ActivityFeedGenerator"); + + /** The time this lock will persist in the database (30 sec but refreshed at regular intervals) */ + private static final long LOCK_TTL = 1000 * 30; + private static VmShutdownListener vmShutdownListener = new VmShutdownListener(AbstractFeedGenerator.class.getName()); private int maxItemsPerCycle = 100; @@ -42,10 +53,12 @@ public abstract class AbstractFeedGenerator implements FeedGenerator private ActivityPostServiceImpl activityPostServiceImpl; private AuthenticationService authenticationService; + private JobLockService jobLockService; + private String repoEndPoint; // http://hostname:port/webapp (eg. http://localhost:8080/alfresco) private boolean userNamesAreCaseSensitive = false; - + private RepoCtx ctx = null; private volatile boolean busy; @@ -84,7 +97,7 @@ public abstract class AbstractFeedGenerator implements FeedGenerator { return this.maxItemsPerCycle; } - + public ActivityPostDAO getPostDaoService() { return this.postDAO; @@ -94,6 +107,12 @@ public abstract class AbstractFeedGenerator implements FeedGenerator { return this.authenticationService; } + + public void setJobLockService(JobLockService jobLockService) + { + this.jobLockService = jobLockService; + } + public RepoCtx getWebScriptsCtx() { @@ -120,21 +139,42 @@ public abstract class AbstractFeedGenerator implements FeedGenerator abstract public int getEstimatedGridSize(); + protected boolean isActive() + { + return busy; + } + public void execute() throws JobExecutionException { - if (busy) - { - logger.warn("Still busy ..."); - return; - } + checkProperties(); + + String lockToken = null; - busy = true; try { - checkProperties(); - + JobLockRefreshCallback lockCallback = new LockCallback(); + lockToken = acquireLock(lockCallback); + + if (logger.isTraceEnabled()) + { + logger.trace("Activities feed generator started"); + } + // run one job cycle generate(); + + if (logger.isTraceEnabled()) + { + logger.trace("Activities feed generator completed"); + } + } + catch (LockAcquisitionException e) + { + // Job being done by another process + if (logger.isDebugEnabled()) + { + logger.debug("Activities feed generator already underway"); + } } catch (Throwable e) { @@ -150,9 +190,66 @@ public abstract class AbstractFeedGenerator implements FeedGenerator } finally { - busy = false; + releaseLock(lockToken); } } - + protected abstract boolean generate() throws Exception; + + private class LockCallback implements JobLockRefreshCallback + { + @Override + public boolean isActive() + { + return busy; + } + + @Override + public void lockReleased() + { + // note: currently the cycle will try to complete (even if refresh failed) + synchronized(this) + { + if (logger.isErrorEnabled()) + { + logger.error("Lock released (refresh failed): " + LOCK_QNAME); + } + + busy = false; + } + } + } + + private String acquireLock(JobLockRefreshCallback lockCallback) throws LockAcquisitionException + { + // Try to get lock + String lockToken = jobLockService.getLock(LOCK_QNAME, LOCK_TTL); + + // Got the lock - now register the refresh callback which will keep the lock alive + jobLockService.refreshLock(lockToken, LOCK_QNAME, LOCK_TTL, lockCallback); + + busy = true; + + if (logger.isDebugEnabled()) + { + logger.debug("lock aquired: " + lockToken); + } + + return lockToken; + } + + private void releaseLock(String lockToken) + { + if (lockToken != null) + { + busy = false; + + jobLockService.releaseLock(lockToken, LOCK_QNAME); + + if (logger.isDebugEnabled()) + { + logger.debug("lock released: " + lockToken); + } + } + } } diff --git a/source/java/org/alfresco/repo/activities/feed/FeedTaskProcessor.java b/source/java/org/alfresco/repo/activities/feed/FeedTaskProcessor.java index 1dddad8752..5d52da4431 100644 --- a/source/java/org/alfresco/repo/activities/feed/FeedTaskProcessor.java +++ b/source/java/org/alfresco/repo/activities/feed/FeedTaskProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -30,7 +30,6 @@ import java.net.URISyntaxException; import java.net.URL; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -44,13 +43,13 @@ import org.alfresco.repo.domain.activities.ActivityFeedEntity; import org.alfresco.repo.domain.activities.ActivityPostEntity; import org.alfresco.repo.domain.activities.FeedControlEntity; import org.alfresco.repo.template.ISO8601DateFormatMethod; -import org.springframework.extensions.surf.util.Base64; import org.alfresco.util.JSONtoFmModel; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import org.springframework.extensions.surf.util.Base64; import freemarker.cache.URLTemplateLoader; import freemarker.template.Configuration; @@ -117,9 +116,8 @@ public abstract class FeedTaskProcessor Configuration cfg = getFreemarkerConfiguration(ctx); Map> activityTemplates = new HashMap>(10); - Map> siteConnectedUsers = new TreeMap>(); - + Map> userFeedControls = new HashMap>(); Map templateCache = new TreeMap(); // for each activity post ... @@ -207,7 +205,16 @@ public abstract class FeedTaskProcessor continue; } - String thisSite = activityPost.getSiteNetwork(); + String thisSite = (activityPost.getSiteNetwork() != null ? activityPost.getSiteNetwork() : ""); + + if (thisSite.length() == 0) + { + // note: although we allow posts without site id - we currently require site context to generate feeds for site members (hence skip here with warning) + // (also Share currently only posts activities within site context) + logger.warn(">>> Skipping activity post " + activityPost.getId() + " since no site"); + updatePostStatus(activityPost.getId(), ActivityPostEntity.STATUS.PROCESSED); + continue; + } model.put(ActivityFeedEntity.KEY_ACTIVITY_FEED_TYPE, activityPost.getActivityType()); model.put(ActivityFeedEntity.KEY_ACTIVITY_FEED_SITE, thisSite); @@ -221,28 +228,21 @@ public abstract class FeedTaskProcessor Set connectedUsers = siteConnectedUsers.get(thisSite); if (connectedUsers == null) { - if ((thisSite == null) || (thisSite.length() == 0)) + try { - connectedUsers = Collections.singleton(""); // add empty posting userid - to represent site feed ! + // Repository callback to get site members + connectedUsers = getSiteMembers(ctx, thisSite); + connectedUsers.add(""); // add empty posting userid - to represent site feed ! } - else + catch(Exception e) { - try - { - // Repository callback to get site members - connectedUsers = getSiteMembers(ctx, thisSite); - connectedUsers.add(""); // add empty posting userid - to represent site feed ! - - // Cache them for future use in this same invocation - siteConnectedUsers.put(thisSite, connectedUsers); - } - catch(Exception e) - { logger.error("Skipping activity post " + activityPost.getId() + " since failed to get site members: " + e); - updatePostStatus(activityPost.getId(), ActivityPostEntity.STATUS.ERROR); - continue; - } + updatePostStatus(activityPost.getId(), ActivityPostEntity.STATUS.ERROR); + continue; } + + // Cache them for future use in this same invocation + siteConnectedUsers.put(thisSite, connectedUsers); } try @@ -261,7 +261,13 @@ public abstract class FeedTaskProcessor List feedControls = null; if (! connectedUser.equals("")) { - feedControls = getFeedControls(connectedUser); + // Get user's feed controls + feedControls = userFeedControls.get(connectedUser); + if (feedControls == null) + { + feedControls = getFeedControls(connectedUser); + userFeedControls.put(connectedUser, feedControls); + } } // filter based on opt-out feed controls (if any) @@ -582,7 +588,6 @@ public abstract class FeedTaskProcessor protected List getFeedControls(String connectedUser) throws SQLException { - // TODO cache for this run return selectUserFeedControls(connectedUser); } diff --git a/source/java/org/alfresco/repo/activities/script/Activity.java b/source/java/org/alfresco/repo/activities/script/Activity.java index f18e816bfe..f4cca0dbe4 100644 --- a/source/java/org/alfresco/repo/activities/script/Activity.java +++ b/source/java/org/alfresco/repo/activities/script/Activity.java @@ -25,6 +25,8 @@ import org.alfresco.service.cmr.activities.ActivityService; import org.alfresco.service.cmr.activities.FeedControl; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; /** * Scripted Activity Service for posting activities. @@ -119,12 +121,17 @@ public final class Activity extends BaseScopableProcessorExtension /** * For current user, get feed controls * - * @return array of user feed controls + * @return JavaScript array of user feed controls */ - public FeedControl[] getFeedControls() + public Scriptable getFeedControls() { List feedControls = activityService.getFeedControls(); - return (FeedControl[])feedControls.toArray(new FeedControl[feedControls.size()]); + Object[] results = new Object[feedControls.size()]; + for (int i=0; i < feedControls.size(); i++) + { + results[i] = feedControls.get(i); + } + return Context.getCurrentContext().newArray(getScope(), results); } /** diff --git a/source/java/org/alfresco/repo/activities/script/test_activityService.js b/source/java/org/alfresco/repo/activities/script/test_activityService.js index 05cce5b1ed..5f3bbc3efc 100644 --- a/source/java/org/alfresco/repo/activities/script/test_activityService.js +++ b/source/java/org/alfresco/repo/activities/script/test_activityService.js @@ -14,23 +14,34 @@ activities.postActivity("test activity type 7", "my site", "my app tool", '{ inv // user feed controls var feedControls = activities.getFeedControls(); + +for(var i=0; i < feedControls.length; i++) +{ + var feedControl = feedControls[i]; + activities.unsetFeedControl(feedControl.getSiteId(), feedControl.getAppToolId()); +} + +feedControls = activities.getFeedControls(); test.assertEquals(0, feedControls.length); activities.setFeedControl("my site", "my app tool"); feedControls = activities.getFeedControls(); test.assertEquals(1, feedControls.length); +test.assertEquals("my site", feedControls[0].getSiteId()); +test.assertEquals("my app tool", feedControls[0].getAppToolId()); activities.setFeedControl("my site", null); feedControls = activities.getFeedControls(); test.assertEquals(2, feedControls.length); +// TODO check all - undefined order activities.setFeedControl("", "my app tool"); feedControls = activities.getFeedControls(); test.assertEquals(3, feedControls.length); - +//TODO check all - undefined order activities.unsetFeedControl("my site", "my app tool"); activities.unsetFeedControl("my site", ""); diff --git a/source/java/org/alfresco/repo/admin/patch/PatchServiceImpl.java b/source/java/org/alfresco/repo/admin/patch/PatchServiceImpl.java index b8cca69d8e..a6706297e5 100644 --- a/source/java/org/alfresco/repo/admin/patch/PatchServiceImpl.java +++ b/source/java/org/alfresco/repo/admin/patch/PatchServiceImpl.java @@ -204,17 +204,6 @@ public class PatchServiceImpl implements PatchService private boolean applyPatchAndDependencies(final Patch patch, Map appliedPatchesById) { String id = patch.getId(); - // check if it has already been done - AppliedPatch appliedPatch = appliedPatchesById.get(id); - if (appliedPatch != null && appliedPatch.getSucceeded()) - { - if (appliedPatch.getWasExecuted() && appliedPatch.getSucceeded()) - { - // It was sucessfully executed - return true; - } - // We give the patch another chance - } // ensure that dependencies have been done List dependencies = patch.getDependsOn(); @@ -227,6 +216,20 @@ public class PatchServiceImpl implements PatchService return false; } } + + // check if it has already been done + AppliedPatch appliedPatch = appliedPatchesById.get(id); + if (appliedPatch != null && appliedPatch.getSucceeded()) + { + if (appliedPatch.getWasExecuted() && appliedPatch.getSucceeded()) + { + // It was sucessfully executed + return true; + } + // We give the patch another chance + } + + // all the dependencies were successful appliedPatch = applyPatch(patch); @@ -402,16 +405,17 @@ public class PatchServiceImpl implements PatchService } /** - * Perform some setup before applying the patch e.g. check whether the patch needs - * to be applied. + * Perform some setup before applying the patch e.g. check whether the patch needs to be applied. * * @return true: continue, false: do not apply patch */ private void setup() { - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { @Override - public Object execute() throws Throwable { + public Object execute() throws Throwable + { final boolean forcePatch = patch.isForce(); if (forcePatch) { @@ -504,7 +508,8 @@ public class PatchServiceImpl implements PatchService return; } - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { @Override public Object execute() throws Throwable { Descriptor serverDescriptor = descriptorService.getServerDescriptor(); @@ -581,6 +586,5 @@ public class PatchServiceImpl implements PatchService Integer i2 = new Integer(p2.getTargetSchema()); return i1.compareTo(i2); } - } } diff --git a/source/java/org/alfresco/repo/admin/patch/impl/FixAclInheritancePatch.java b/source/java/org/alfresco/repo/admin/patch/impl/FixAclInheritancePatch.java new file mode 100644 index 0000000000..d60896494e --- /dev/null +++ b/source/java/org/alfresco/repo/admin/patch/impl/FixAclInheritancePatch.java @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.admin.patch.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.repo.domain.patch.PatchDAO; +import org.alfresco.repo.domain.permissions.AccessControlListDAO; +import org.alfresco.repo.domain.permissions.AclDAO; +import org.alfresco.repo.security.permissions.ACLType; +import org.alfresco.repo.security.permissions.AccessControlListProperties; +import org.alfresco.repo.security.permissions.impl.AclChange; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * Fix ACLs that inherit and have issues with inheritance to correctly inherit from their primary parent, that may have + * failed on upgrade or that have any other issue according to the DB + */ +public class FixAclInheritancePatch extends AbstractPatch +{ + private static final String MSG_SUCCESS = "patch.fixAclInheritance.result"; + + private static Log logger = LogFactory.getLog(FixAclInheritancePatch.class); + + private AclDAO aclDAO; + + private PatchDAO patchDAO; + + private AccessControlListDAO accessControlListDao; + + private RetryingTransactionHelper retryingTransactionHelper; + + private long count = 0; + + /** + * @param aclDaoComponent + * the aclDaoComponent to set + */ + public void setAclDAO(AclDAO aclDAO) + { + this.aclDAO = aclDAO; + } + + public void setPatchDAO(PatchDAO patchDAO) + { + this.patchDAO = patchDAO; + } + + public void setAccessControlListDao(AccessControlListDAO accessControlListDao) + { + this.accessControlListDao = accessControlListDao; + } + + /** + * @param retryingTransactionHelper + * the retryingTransactionHelper to set + */ + public void setRetryingTransactionHelper(RetryingTransactionHelper retryingTransactionHelper) + { + this.retryingTransactionHelper = retryingTransactionHelper; + } + + @Override + protected String applyInternal() throws Exception + { + // Fix unwired inheritance first as the other fixes depend on it and the fix can create D-D issues + + List> rows = retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback>>() + { + + @Override + public List> execute() throws Throwable + { + return patchDAO.getAclsThatInheritWithInheritanceUnset(); + } + }, false, true); + + for (Map row : rows) + { + Long childAclId = (Long) row.get("childAclId"); + Long childAclType = (Long) row.get("childAclType"); + Long primaryParentAclId = (Long) row.get("primaryParentAclId"); + Long primaryParentAclType = (Long) row.get("primaryParentAclType"); + Long childNodeId = (Long) row.get("childNodeId"); + + ACLType childType = ACLType.getACLTypeFromId(childAclType.intValue()); + ACLType parentType = ACLType.getACLTypeFromId(primaryParentAclType.intValue()); + + RetryingTransactionCallback cb = null; + + switch (childType) + { + case DEFINING: + cb = new FixInherited(primaryParentAclId, childAclId); + retryingTransactionHelper.doInTransaction(cb, false, true); + count++; + break; + case FIXED: + break; + case GLOBAL: + break; + case LAYERED: + break; + case OLD: + break; + case SHARED: + cb = new FixSharedUnsetInheritanceCallback(childNodeId, primaryParentAclId, childAclId); + retryingTransactionHelper.doInTransaction(cb, false, true); + count++; + break; + } + + } + + // If we fixed up any D - S relationships with and create a new inherited ACL we may break some D - D + // relationships + // Fix these up after as they appear as broken inheritance + + rows = retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback>>() + { + + @Override + public List> execute() throws Throwable + { + return patchDAO.getSharedAclsThatDoNotInheritCorrectlyFromTheirDefiningAcl(); + } + }, false, true); + + for (Map row : rows) + { + Long inheritedAclId = (Long) row.get("inheritedAclId"); + Long inheritedAclType = (Long) row.get("inheritedAclType"); + Long aclId = (Long) row.get("aclId"); + Long aclType = (Long) row.get("aclType"); + + ACLType inheritedType = ACLType.getACLTypeFromId(inheritedAclType.intValue()); + ACLType type = ACLType.getACLTypeFromId(aclType.intValue()); + + FixSharedAclCallback cb = new FixSharedAclCallback(inheritedAclId, aclId); + retryingTransactionHelper.doInTransaction(cb, false, true); + count++; + } + + rows = retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback>>() + { + + @Override + public List> execute() throws Throwable + { + return patchDAO.getSharedAclsThatDoNotInheritCorrectlyFromThePrimaryParent(); + } + }, false, true); + + for (Map row : rows) + { + Long childAclId = (Long) row.get("childAclId"); + Long childAclType = (Long) row.get("childAclType"); + Long primaryParentAclId = (Long) row.get("primaryParentAclId"); + Long primaryParentAclType = (Long) row.get("primaryParentAclType"); + Long childNodeId = (Long) row.get("childNodeId"); + + ACLType childType = ACLType.getACLTypeFromId(childAclType.intValue()); + ACLType parentType = ACLType.getACLTypeFromId(primaryParentAclType.intValue()); + + FixSharedAclCallback cb = new FixSharedAclCallback(primaryParentAclId, childAclId); + retryingTransactionHelper.doInTransaction(cb, false, true); + count++; + } + + rows = retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback>>() + { + + @Override + public List> execute() throws Throwable + { + return patchDAO.getDefiningAclsThatDoNotInheritCorrectlyFromThePrimaryParent(); + } + }, false, true); + + for (Map row : rows) + { + Long childAclId = (Long) row.get("childAclId"); + Long childAclType = (Long) row.get("childAclType"); + Long primaryParentAclId = (Long) row.get("primaryParentAclId"); + Long primaryParentAclType = (Long) row.get("primaryParentAclType"); + Long childNodeId = (Long) row.get("childNodeId"); + + ACLType childType = ACLType.getACLTypeFromId(childAclType.intValue()); + ACLType parentType = ACLType.getACLTypeFromId(primaryParentAclType.intValue()); + + FixInherited cb = new FixInherited(primaryParentAclId, childAclId); + retryingTransactionHelper.doInTransaction(cb, false, true); + count++; + break; + + } + + rows = retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback>>() + { + + @Override + public List> execute() throws Throwable + { + return patchDAO.getAclsThatInheritFromNonPrimaryParent(); + } + }, false, true); + + for (Map row : rows) + { + Long childAclId = (Long) row.get("childAclId"); + Long childAclType = (Long) row.get("childAclType"); + Long primaryParentAclId = (Long) row.get("primaryParentAclId"); + Long primaryParentAclType = (Long) row.get("primaryParentAclType"); + Long childNodeId = (Long) row.get("childNodeId"); + + ACLType childType = ACLType.getACLTypeFromId(childAclType.intValue()); + ACLType parentType = ACLType.getACLTypeFromId(primaryParentAclType.intValue()); + + RetryingTransactionCallback cb = null; + switch (childType) + { + case DEFINING: + cb = new FixInherited(primaryParentAclId, childAclId); + retryingTransactionHelper.doInTransaction(cb, false, true); + count++; + break; + case FIXED: + break; + case GLOBAL: + break; + case LAYERED: + break; + case OLD: + break; + case SHARED: + cb = new SetFixedAclsCallback(childNodeId, primaryParentAclId, childAclId); + retryingTransactionHelper.doInTransaction(cb, false, true); + count++; + break; + } + + } + + // build the result message + String msg = I18NUtil.getMessage(FixAclInheritancePatch.MSG_SUCCESS, count); + // done + return msg; + + } + + private class FixSharedUnsetInheritanceCallback implements RetryingTransactionCallback + { + + Long childNodeId; + + Long primaryParentAclId; + + Long childAclId; + + FixSharedUnsetInheritanceCallback(Long childNodeId, Long primaryParentAclId, Long childAclId) + { + this.childNodeId = childNodeId; + this.primaryParentAclId = primaryParentAclId; + this.childAclId = childAclId; + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback#execute() + */ + @Override + public Void execute() throws Throwable + { + Long inheritedAclId = aclDAO.getInheritedAccessControlList(primaryParentAclId); + if (inheritedAclId.equals(childAclId)) + { + // child acl does match inherited from primary parent + // needs to set inherits_from correctly and fix up + aclDAO.fixSharedAcl(primaryParentAclId, childAclId); + } + else + { + // child acl does not match inherited from primary parent + // need to replace the shared acl + List changes = new ArrayList(); + accessControlListDao.setFixedAcls(childNodeId, primaryParentAclId, null, childAclId, changes, true); + } + return null; + } + + } + + private class SetFixedAclsCallback implements RetryingTransactionCallback + { + Long childNodeId; + + Long primaryParentAclId; + + Long childAclId; + + SetFixedAclsCallback(Long childNodeId, Long primaryParentAclId, Long childAclId) + { + this.childNodeId = childNodeId; + this.primaryParentAclId = primaryParentAclId; + this.childAclId = childAclId; + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback#execute() + */ + @Override + public Void execute() throws Throwable + { + List changes = new ArrayList(); + accessControlListDao.setFixedAcls(childNodeId, primaryParentAclId, null, childAclId, changes, true); + return null; + } + } + + private class FixSharedAclCallback implements RetryingTransactionCallback + { + + Long inheritedAclId; + + Long aclId; + + FixSharedAclCallback(Long inheritedAclId, Long aclId) + { + this.inheritedAclId = inheritedAclId; + this.aclId = aclId; + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback#execute() + */ + @Override + public Void execute() throws Throwable + { + aclDAO.fixSharedAcl(inheritedAclId, aclId); + return null; + } + + } + + private class FixInherited implements RetryingTransactionCallback + { + + Long primaryParentAclId; + + Long childAclId; + + FixInherited(Long primaryParentAclId, Long childAclId) + { + this.primaryParentAclId = primaryParentAclId; + this.childAclId = childAclId; + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback#execute() + */ + @Override + public Void execute() throws Throwable + { + aclDAO.enableInheritance(childAclId, primaryParentAclId); + return null; + } + } +} diff --git a/source/java/org/alfresco/repo/admin/patch/impl/SitePermissionRefactorPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/SitePermissionRefactorPatch.java index 2180547e2c..8c33a2de32 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/SitePermissionRefactorPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/SitePermissionRefactorPatch.java @@ -21,10 +21,8 @@ package org.alfresco.repo.admin.patch.impl; import java.util.List; import java.util.Set; -import org.springframework.extensions.surf.util.I18NUtil; import org.alfresco.repo.admin.patch.AbstractPatch; import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.site.SiteModel; import org.alfresco.repo.site.SiteServiceImpl; import org.alfresco.service.cmr.security.AccessPermission; import org.alfresco.service.cmr.security.AuthorityService; @@ -32,6 +30,8 @@ import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.site.SiteInfo; import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.namespace.QName; +import org.springframework.extensions.surf.util.I18NUtil; /** * Patch's the site permission model to use groups to contain users. @@ -96,7 +96,8 @@ public class SitePermissionRefactorPatch extends AbstractPatch AuthorityType.GROUP, ((SiteServiceImpl)this.siteService).getSiteGroup(siteInfo.getShortName(), false)); - Set permissions = permissionService.getSettablePermissions(SiteModel.TYPE_SITE); + QName siteType = nodeService.getType(siteInfo.getNodeRef()); + Set permissions = permissionService.getSettablePermissions(siteType); for (String permission : permissions) { // Create a group for the permission diff --git a/source/java/org/alfresco/repo/avm/AVMNodeConverter.java b/source/java/org/alfresco/repo/avm/AVMNodeConverter.java index 57bc9d02be..c05d7e9489 100644 --- a/source/java/org/alfresco/repo/avm/AVMNodeConverter.java +++ b/source/java/org/alfresco/repo/avm/AVMNodeConverter.java @@ -50,7 +50,7 @@ public class AVMNodeConverter } StoreRef storeRef = ToStoreRef(pathParts[0]); String translated = version + pathParts[1]; - translated = translated.replaceAll("/+", ";"); + translated = translated.replaceAll("/+", "|"); return new NodeRef(storeRef, translated); } @@ -73,7 +73,17 @@ public class AVMNodeConverter { StoreRef store = nodeRef.getStoreRef(); String translated = nodeRef.getId(); - translated = translated.replace(';', AVMUtil.AVM_PATH_SEPARATOR_CHAR); + + if (translated.indexOf('|') != -1) + { + // we assume that this is the new style avm path + translated = translated.replace('|', AVMUtil.AVM_PATH_SEPARATOR_CHAR); + } + else + { + // this is the old style avm path + translated = translated.replace(';', AVMUtil.AVM_PATH_SEPARATOR_CHAR); + } int off = translated.indexOf(AVMUtil.AVM_PATH_SEPARATOR_CHAR); if (off == -1) { diff --git a/source/java/org/alfresco/repo/avm/AVMRepository.java b/source/java/org/alfresco/repo/avm/AVMRepository.java index 22364fff11..6b4cc1e853 100644 --- a/source/java/org/alfresco/repo/avm/AVMRepository.java +++ b/source/java/org/alfresco/repo/avm/AVMRepository.java @@ -229,7 +229,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); OutputStream out = store.createFile(pathParts[1], name); @@ -260,7 +260,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); store.createFile(pathParts[1], name, data, aspects, properties); @@ -288,7 +288,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); store.createDirectory(pathParts[1], name, aspects, properties); @@ -329,7 +329,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } DirectoryNode dir = (DirectoryNode) node; DirectoryNode child = null; @@ -384,7 +384,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); store.createLayeredDirectory(srcPath, pathParts[1], name); @@ -414,7 +414,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); store.createLayeredFile(srcPath, pathParts[1], name); @@ -489,7 +489,7 @@ public class AVMRepository AVMStore srcRepo = getAVMStoreByName(pathParts[0]); if (srcRepo == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } if (version < 0) { @@ -516,7 +516,7 @@ public class AVMRepository AVMStore dstRepo = getAVMStoreByName(pathParts[0]); if (dstRepo == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); Lookup dPath = dstRepo.lookupDirectory(-1, pathParts[1], true); @@ -601,7 +601,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); OutputStream out = store.getOutputStream(pathParts[1]); @@ -631,7 +631,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found: " + pathParts[0]); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } return store.getContentReader(version, pathParts[1]); } @@ -660,7 +660,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found: " + pathParts[0]); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); ContentWriter writer = store.createContentWriter(pathParts[1], update); @@ -703,7 +703,7 @@ public class AVMRepository AVMStore srcRepo = getAVMStoreByName(pathParts[0]); if (srcRepo == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } sPath = srcRepo.lookupDirectory(-1, pathParts[1], true); if (sPath == null) @@ -736,7 +736,7 @@ public class AVMRepository AVMStore dstRepo = getAVMStoreByName(pathParts[0]); if (dstRepo == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } Lookup dPath = dstRepo.lookupDirectory(-1, pathParts[1], true); if (dPath == null) @@ -916,7 +916,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); store.uncover(pathParts[1], name); @@ -944,7 +944,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(storeName); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+storeName); } Map result = store.createSnapshot(tag, description, new HashMap()); for (Map.Entry entry : result.entrySet()) @@ -972,7 +972,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onDelete(pathParts[0]); store.removeNode(pathParts[1], name); @@ -995,7 +995,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(name); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+name); } fLookupCache.onDelete(name); AVMNode root = store.getRoot(); @@ -1051,7 +1051,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(name); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+name); } fLookupCache.onDelete(name); store.purgeVersion(version); @@ -1081,7 +1081,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } return store.getInputStream(version, pathParts[1]); } @@ -1132,7 +1132,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } return store.getListing(version, pathParts[1], includeDeleted); } @@ -1160,7 +1160,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } return store.getListingDirect(version, pathParts[1], includeDeleted); } @@ -1291,7 +1291,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } return store.getDeleted(version, pathParts[1]); } @@ -1351,7 +1351,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(name); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+name); } return store.getVersions(); } @@ -1372,7 +1372,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(name); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+name); } return store.getVersions(from, to); } @@ -1395,7 +1395,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } return store.getIndirectionPath(version, pathParts[1]); } @@ -1417,7 +1417,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(name); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+name); } return store.getNextVersionID(); } @@ -1433,7 +1433,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(name); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+name); } return store.getLastVersionID(); } @@ -1681,7 +1681,7 @@ public class AVMRepository AVMStore st = getAVMStoreByName(store); if (st == null) { - throw new AVMNotFoundException("Store not found: " + store); + throw new AVMNotFoundException("Store not found: "+store); } AVMNode node = fAVMNodeDAO.getByID(desc.getId()); if (node == null) @@ -1934,7 +1934,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } Lookup lookup = store.lookup(version, pathParts[1], false, true); if (lookup == null) @@ -2020,7 +2020,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); store.makePrimary(pathParts[1]); @@ -2048,7 +2048,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); store.retargetLayeredDirectory(pathParts[1], target); @@ -2122,7 +2122,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); store.setOpacity(pathParts[1], opacity); @@ -2152,7 +2152,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); store.setNodeProperty(pathParts[1], name, value); @@ -2180,7 +2180,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); store.setNodeProperties(pathParts[1], properties); @@ -2211,7 +2211,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } return store.getNodeProperty(version, pathParts[1], name); } @@ -2239,7 +2239,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } return store.getNodeProperties(version, pathParts[1]); } @@ -2266,7 +2266,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); store.deleteNodeProperty(pathParts[1], name); @@ -2292,7 +2292,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); store.deleteNodeProperties(pathParts[1]); @@ -2318,7 +2318,7 @@ public class AVMRepository AVMStore st = getAVMStoreByName(store); if (st == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+store); } st.setProperty(name, value); } @@ -2336,7 +2336,7 @@ public class AVMRepository AVMStore st = getAVMStoreByName(store); if (st == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+store); } st.setProperties(props); } @@ -2359,7 +2359,7 @@ public class AVMRepository AVMStore st = getAVMStoreByName(store); if (st == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+store); } return st.getProperty(name); } @@ -2378,7 +2378,7 @@ public class AVMRepository AVMStore st = getAVMStoreByName(store); if (st == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+store); } return fAVMStorePropertyDAO.queryByKeyPattern(st, keyPattern); @@ -2412,7 +2412,7 @@ public class AVMRepository AVMStore st = getAVMStoreByName(store); if (st == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+store); } return st.getProperties(); } @@ -2430,7 +2430,7 @@ public class AVMRepository AVMStore st = getAVMStoreByName(store); if (st == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+store); } st.deleteProperty(name); } @@ -2550,7 +2550,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); ContentData result = store.getContentDataForWrite(pathParts[1]); @@ -2579,7 +2579,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); store.setContentData(pathParts[1], data); @@ -2609,7 +2609,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found: " + pathParts[0]); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } AVMNode fromNode = fAVMNodeDAO.getByID(from.getId()); if (fromNode == null) @@ -2642,7 +2642,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); store.addAspect(pathParts[1], aspectName); @@ -2671,7 +2671,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } return store.getAspects(version, pathParts[1]); } @@ -2698,7 +2698,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); store.removeAspect(pathParts[1], aspectName); @@ -2729,7 +2729,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } return store.hasAspect(version, pathParts[1], aspectName); } @@ -2756,7 +2756,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); store.setACL(pathParts[1], acl); @@ -2785,7 +2785,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } return store.getACL(version, pathParts[1]); } @@ -2814,7 +2814,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } store.link(pathParts[1], name, toLink); fLookupCache.onWrite(pathParts[0]); @@ -2844,7 +2844,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } store.updateLink(pathParts[1], name, toLink); fLookupCache.onWrite(pathParts[0]); @@ -2902,7 +2902,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onDelete(pathParts[0]); Lookup lPath = store.lookup(-1, pathParts[1], true, false); @@ -2943,7 +2943,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); // Just force a copy if needed by looking up in write mode. @@ -2979,7 +2979,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(sourceName); if (store == null) { - throw new AVMNotFoundException("Store Not Found: " + sourceName); + throw new AVMNotFoundException("Store Not Found: "+sourceName); } if (getAVMStoreByName(destName) != null) { @@ -3020,7 +3020,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found: " + pathParts[0]); + throw new AVMNotFoundException("Store not found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); store.revert(pathParts[1], name, toRevertTo); @@ -3046,7 +3046,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store not found:" + pathParts[0]); + throw new AVMNotFoundException("Store not found:"+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); store.setGuid(pathParts[1], guid); @@ -3072,7 +3072,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store Not Found: " + pathParts[0]); + throw new AVMNotFoundException("Store Not Found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); store.setEncoding(pathParts[1], encoding); @@ -3098,7 +3098,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(pathParts[0]); if (store == null) { - throw new AVMNotFoundException("Store Not Found: " + pathParts[0]); + throw new AVMNotFoundException("Store Not Found: "+pathParts[0]); } fLookupCache.onWrite(pathParts[0]); store.setMimeType(pathParts[1], mimeType); @@ -3401,7 +3401,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(storeName); if (store == null) { - throw new AVMNotFoundException("Store not found: " + storeName); + throw new AVMNotFoundException("Store not found: "+storeName); } store.setStoreAcl(acl); @@ -3419,7 +3419,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(storeName); if (store == null) { - throw new AVMNotFoundException("Store not found: " + storeName); + throw new AVMNotFoundException("Store not found: "+storeName); } return store.getStoreAcl(); } @@ -3434,7 +3434,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(name); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+name); } return store.getVersionsTo(version); } @@ -3449,7 +3449,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(name); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+name); } return store.getVersionsFrom(version); } @@ -3465,7 +3465,7 @@ public class AVMRepository AVMStore store = getAVMStoreByName(name); if (store == null) { - throw new AVMNotFoundException("Store not found."); + throw new AVMNotFoundException("Store not found: "+name); } return store.getVersionsBetween(startVersion, endVersion); } diff --git a/source/java/org/alfresco/repo/avm/AVMServicePermissionsTest.java b/source/java/org/alfresco/repo/avm/AVMServicePermissionsTest.java index ae318e66ab..17bd4b1632 100644 --- a/source/java/org/alfresco/repo/avm/AVMServicePermissionsTest.java +++ b/source/java/org/alfresco/repo/avm/AVMServicePermissionsTest.java @@ -48,6 +48,8 @@ import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.avmsync.AVMDifference; import org.alfresco.service.cmr.avmsync.AVMSyncService; import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; @@ -115,6 +117,8 @@ public class AVMServicePermissionsTest extends TestCase private AVMSyncService avmSyncService; + private ContentService contentService; + public AVMServicePermissionsTest() { super(); @@ -141,6 +145,7 @@ public class AVMServicePermissionsTest extends TestCase permissionModelDAO = (ModelDAO) applicationContext.getBean("permissionsModelDAO"); personService = (PersonService) applicationContext.getBean("personService"); authorityService = (AuthorityService) applicationContext.getBean("authorityService"); + contentService = (ContentService) applicationContext.getBean("contentService"); authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); authenticationDAO = (MutableAuthenticationDao) applicationContext.getBean("authenticationDao"); @@ -403,6 +408,93 @@ public class AVMServicePermissionsTest extends TestCase // test setUp & tearDown } + + public void test_ETWOTWO_457_NPE1() throws Exception + { + try + { + // run as system (null) + authenticationService.clearCurrentSecurityContext(); + + avmService.createStore("main"); + avmService.createDirectory("main:/", "a"); + + // java.lang.NullPointerException - at org.alfresco.service.cmr.security.AuthorityType.getAuthorityType(AuthorityType.java:254) + Set perms = permissionService.getPermissions(AVMNodeConverter.ToNodeRef(-1, "main:/a")); + for (AccessPermission permission : perms) + { + System.out.println(permission); + } + } + catch (Exception e) + { + e.printStackTrace(System.err); + throw e; + } + finally + { + avmService.purgeStore("main"); + } + } + + public void test_ETWOTWO_457_NPE2() throws Exception + { + try + { + // run as admin + authenticationService.authenticate("admin", "admin".toCharArray()); + + avmService.createStore("main"); + avmService.createDirectory("main:/", "a"); + + Set perms = permissionService.getPermissions(AVMNodeConverter.ToNodeRef(-1, "main:/a")); + for (AccessPermission permission : perms) + { + System.out.println(permission); + } + + // java.lang.NullPointerException - at org.alfresco.repo.domain.hibernate.AbstractPermissionsDaoComponentImpl.deletePermission(AbstractPermissionsDaoComponentImpl.java:383) + permissionService.deletePermission(AVMNodeConverter.ToNodeRef(-1, "main:/a"), PermissionService.ADMINISTRATOR_AUTHORITY, PermissionService.ALL_PERMISSIONS); + } + catch (Exception e) + { + e.printStackTrace(System.err); + throw e; + } + finally + { + avmService.purgeStore("main"); + } + } + + public void test_ETWOTWO_457_NPE3() throws Exception + { + try + { + // run as system (null) + authenticationService.clearCurrentSecurityContext(); + authenticationComponent.setSystemUserAsCurrentUser(); + + avmService.createStore("main"); + avmService.createFile("main:/", "foo").close(); + + permissionService.setPermission(AVMNodeConverter.ToNodeRef(-1, "main:/").getStoreRef(), PermissionService.ALL_AUTHORITIES, PermissionService.ALL_PERMISSIONS, true); + + // java.lang.NullPointerException - at org.alfresco.repo.security.permissions.impl.PermissionServiceImpl.hasPermission(PermissionServiceImpl.java:494) + ContentReader cr = contentService.getReader(AVMNodeConverter.ToNodeRef(-1, "main:/foo"), ContentModel.PROP_CONTENT); + } + catch (Exception e) + { + e.printStackTrace(System.err); + throw e; + } + finally + { + avmService.purgeStore("main"); + } + } + + public void testStoreAcls() throws Exception { runAs(AuthenticationUtil.getAdminUserName()); diff --git a/source/java/org/alfresco/repo/avm/AVMStressTestP.java b/source/java/org/alfresco/repo/avm/AVMStressTestP.java index ae9004a151..b890ba887c 100644 --- a/source/java/org/alfresco/repo/avm/AVMStressTestP.java +++ b/source/java/org/alfresco/repo/avm/AVMStressTestP.java @@ -56,8 +56,8 @@ public class AVMStressTestP extends AVMServiceTestBase 10, // create file 2, // create dir 2, // rename - 0, // create layered dir // TODO pending ETWOTWO-715 (is 2 in 2.1.x) - 0, // create layered file // TODO pending ETWOTWO-715 (is 2 in 2.1.x) + 2, // create layered dir // TODO pending ETWOTWO-715 (is 2 in 2.1.x) + 2, // create layered file // TODO pending ETWOTWO-715 (is 2 in 2.1.x) 5, // remove node 10, // modify file 50, // read file diff --git a/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImpl.java b/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImpl.java index 504fef28e9..1884987a48 100644 --- a/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImpl.java +++ b/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImpl.java @@ -482,25 +482,42 @@ public class CheckOutCheckInServiceImpl implements CheckOutCheckInService NodeRef workingCopy = null; ruleService.disableRuleType(RuleType.UPDATE); try - { + { // Make the working copy final QName copyQName = QName.createQName(destinationAssocQName.getNamespaceURI(), QName.createValidLocalName(copyName)); - workingCopy = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + + // Find the primary parent + ChildAssociationRef childAssocRef = this.nodeService.getPrimaryParent(nodeRef); + + // If destination parent for working copy is the same as the parent of the source node + // then working copy should be created even if the user has no permissions to create children in + // the parent of the source node + if (destinationParentNodeRef.equals(childAssocRef.getParentRef())) { - public NodeRef doWork() throws Exception + workingCopy = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() { - NodeRef copy = copyService.copy( - nodeRef, - destinationParentNodeRef, - destinationAssocTypeQName, + public NodeRef doWork() throws Exception + { + NodeRef copy = copyService.copy( + nodeRef, + destinationParentNodeRef, + destinationAssocTypeQName, copyQName); - - // Set the owner of the working copy to be the current user - ownableService.setOwner(copy, userName); - return copy; - } - }, AuthenticationUtil.getSystemUserName()); - + + // Set the owner of the working copy to be the current user + ownableService.setOwner(copy, userName); + return copy; + } + }, AuthenticationUtil.getSystemUserName()); + } + else + { + workingCopy = copyService.copy( + nodeRef, + destinationParentNodeRef, + destinationAssocTypeQName, + copyQName); + } // Update the working copy name diff --git a/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImplTest.java b/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImplTest.java index da2b631141..54901611c0 100644 --- a/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImplTest.java +++ b/source/java/org/alfresco/repo/coci/CheckOutCheckInServiceImplTest.java @@ -631,4 +631,149 @@ public class CheckOutCheckInServiceImplTest extends BaseSpringTest modifieer = nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIER); assertEquals("The modifier should change to Admin after checkin!", adminUser, modifieer); } + + public void testCheckOutPermissions_ALF7680_ALF535() + { + /* + * Testing working copy creation in folder of source node. + * User has no permissions to create children in this folder. + * User has permissions to edit document. + * Expected result: working copy should be created. + */ + + NodeRef folder1 = createFolderWithPermission(rootNodeRef, userName, PermissionService.CONSUMER); + NodeRef node = createNodeWithPermission(folder1, userName, PermissionService.EDITOR); + + // Check out the node + NodeRef workingCopy = this.cociService.checkout( + node, + folder1, + ContentModel.ASSOC_CHILDREN, + QName.createQName("workingCopy")); + + // Ensure that the working copy was created and current user was set as owner + assertNotNull(workingCopy); + assertTrue(this.nodeService.hasAspect(workingCopy, ContentModel.ASPECT_WORKING_COPY)); + assertEquals(this.userNodeRef, this.nodeService.getProperty(workingCopy, ContentModel.PROP_WORKING_COPY_OWNER)); + + this.cociService.cancelCheckout(workingCopy); + + /* + * Testing working copy creation in a different folder. + * User has permissions to create children in this folder. + * User has permissions to edit document. + * Expected result: working copy should be created. + */ + + NodeRef folder2 = createFolderWithPermission(rootNodeRef, userName, PermissionService.ALL_PERMISSIONS); + + // Check out the node + workingCopy = this.cociService.checkout( + node, + folder2, + ContentModel.ASSOC_CHILDREN, + QName.createQName("workingCopy")); + + // Ensure that the working copy was created and current user was set as owner + assertNotNull(workingCopy); + assertTrue(this.nodeService.hasAspect(workingCopy, ContentModel.ASPECT_WORKING_COPY)); + assertEquals(this.userNodeRef, this.nodeService.getProperty(workingCopy, ContentModel.PROP_WORKING_COPY_OWNER)); + + this.cociService.cancelCheckout(workingCopy); + + /* + * Testing working copy creation in a different folder. + * User has no permissions to create children in this folder. + * User has permissions to edit document. + * Expected result: exception. + */ + + NodeRef folder3 = createFolderWithPermission(rootNodeRef, userName, PermissionService.CONSUMER); + try + { + // Check out the node + workingCopy = this.cociService.checkout( + node, + folder3, + ContentModel.ASSOC_CHILDREN, + QName.createQName("workingCopy")); + + // Ensure that the working copy was not created and exception occurs + fail("Node can't be checked out to folder where user has no permissions to create children"); + } + catch (Exception e) + { + // Exception is expected + } + + /* + * Testing working copy creation in a different folder. + * User has permissions to create children in this folder. + * User has no permissions to edit document. + * Expected result: exception. + */ + + NodeRef node2 = createNodeWithPermission(folder3, userName, PermissionService.CONSUMER); + try + { + // Check out the node + workingCopy = this.cociService.checkout( + node2, + folder3, + ContentModel.ASSOC_CHILDREN, + QName.createQName("workingCopy")); + + // Ensure that the working copy was not created and exception occurs + fail("Node can't be checked out if user has no permissions to edit document"); + } + catch (Exception e) + { + // Exception is expected + } + } + + private NodeRef createFolderWithPermission(NodeRef parent, String username, String permission) + { + // Authenticate as system user because the current user should not be node owner + AuthenticationComponent authenticationComponent = (AuthenticationComponent) this.applicationContext.getBean("authenticationComponent"); + authenticationComponent.setSystemUserAsCurrentUser(); + + // Create the folder + NodeRef folder = this.nodeService.createNode( + parent, + ContentModel.ASSOC_CHILDREN, + QName.createQName("TestFolder" + GUID.generate()), + ContentModel.TYPE_CONTENT).getChildRef(); + + // Apply permissions to folder + permissionService.deletePermissions(folder); + permissionService.setInheritParentPermissions(folder, false); + permissionService.setPermission(folder, userName, permission, true); + + // Authenticate test user + TestWithUserUtils.authenticateUser(this.userName, PWD, this.rootNodeRef, this.authenticationService); + + return folder; + } + + private NodeRef createNodeWithPermission(NodeRef parent, String username, String permission) + { + // Authenticate as system user because the current user should not be node owner + AuthenticationComponent authenticationComponent = (AuthenticationComponent) this.applicationContext.getBean("authenticationComponent"); + authenticationComponent.setSystemUserAsCurrentUser(); + + // Create the node as a copy of prepared + NodeRef node = copyService.copy(nodeRef, parent, ContentModel.ASSOC_CHILDREN, ContentModel.TYPE_CONTENT); + + // Apply permissions to node + permissionService.deletePermissions(node); + permissionService.setInheritParentPermissions(node, false); + permissionService.setPermission(node, userName, permission, true); + + // Authenticate test user + TestWithUserUtils.authenticateUser(this.userName, PWD, this.rootNodeRef, this.authenticationService); + + return node; + } + } diff --git a/source/java/org/alfresco/repo/content/AbstractContentAccessor.java b/source/java/org/alfresco/repo/content/AbstractContentAccessor.java index 251488c733..b586890d02 100644 --- a/source/java/org/alfresco/repo/content/AbstractContentAccessor.java +++ b/source/java/org/alfresco/repo/content/AbstractContentAccessor.java @@ -30,9 +30,6 @@ import java.util.List; import java.util.Locale; import org.alfresco.error.StackTraceUtil; -import org.springframework.extensions.surf.util.I18NUtil; -import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.repository.ContentAccessor; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentIOException; @@ -41,6 +38,7 @@ import org.alfresco.util.ParameterCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.AfterReturningAdvice; +import org.springframework.extensions.surf.util.I18NUtil; /** * Provides basic support for content accessors. diff --git a/source/java/org/alfresco/repo/content/metadata/AbstractMappingMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/AbstractMappingMetadataExtracter.java index 91559a52b8..2438ef8631 100644 --- a/source/java/org/alfresco/repo/content/metadata/AbstractMappingMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/AbstractMappingMetadataExtracter.java @@ -214,7 +214,6 @@ abstract public class AbstractMappingMetadataExtracter implements MetadataExtrac * is called, this may not be relevant, i.e an empty map of existing properties may be passed * in by the client code, which may follow its own overwrite strategy. * - * TODO - This doesn't appear to be used, so should be removed / deprecated / replaced * @param overwritePolicy the policy to apply when there are existing system properties */ public void setOverwritePolicy(OverwritePolicy overwritePolicy) @@ -227,7 +226,6 @@ abstract public class AbstractMappingMetadataExtracter implements MetadataExtrac * is called, this may not be relevant, i.e an empty map of existing properties may be passed * in by the client code, which may follow its own overwrite strategy. * - * TODO - This doesn't appear to be used, so should be removed / deprecated / replaced * @param overwritePolicyStr the policy to apply when there are existing system properties */ public void setOverwritePolicy(String overwritePolicyStr) @@ -428,12 +426,11 @@ abstract public class AbstractMappingMetadataExtracter implements MetadataExtrac * * @see #setMappingProperties(Properties) */ - @SuppressWarnings("unchecked") protected Map> readMappingProperties(Properties mappingProperties) { Map namespacesByPrefix = new HashMap(5); // Get the namespaces - for (Map.Entry entry : mappingProperties.entrySet()) + for (Map.Entry entry : mappingProperties.entrySet()) { String propertyName = (String) entry.getKey(); if (propertyName.startsWith(NAMESPACE_PROPERTY_PREFIX)) @@ -445,7 +442,7 @@ abstract public class AbstractMappingMetadataExtracter implements MetadataExtrac } // Create the mapping Map> convertedMapping = new HashMap>(17); - for (Map.Entry entry : mappingProperties.entrySet()) + for (Map.Entry entry : mappingProperties.entrySet()) { String documentProperty = (String) entry.getKey(); String qnamesStr = (String) entry.getValue(); @@ -805,7 +802,7 @@ abstract public class AbstractMappingMetadataExtracter implements MetadataExtrac } else if (propertyValue instanceof Collection) { - convertedPropertyValue = (Serializable) makeDates((Collection) propertyValue); + convertedPropertyValue = (Serializable) makeDates((Collection) propertyValue); } else if (propertyValue instanceof String) { @@ -828,7 +825,7 @@ abstract public class AbstractMappingMetadataExtracter implements MetadataExtrac { convertedPropertyValue = (Serializable) DefaultTypeConverter.INSTANCE.convert( propertyTypeDef, - (Collection) propertyValue); + (Collection) propertyValue); } else if (propertyValue instanceof Object[]) { @@ -847,6 +844,11 @@ abstract public class AbstractMappingMetadataExtracter implements MetadataExtrac } catch (TypeConversionException e) { + logger.warn( + "Type conversion failed during metadata extraction: \n" + + " Failure: " + e.getMessage() + "\n" + + " Type: " + propertyTypeDef + "\n" + + " Value: " + propertyValue); // Do we just absorb this or is it a problem? if (failOnTypeConversion) { @@ -933,7 +935,6 @@ abstract public class AbstractMappingMetadataExtracter implements MetadataExtrac * @param destination the map to put values into * @return Returns true if set, otherwise false */ - @SuppressWarnings("unchecked") protected boolean putRawValue(String key, Serializable value, Map destination) { if (value == null) @@ -955,7 +956,7 @@ abstract public class AbstractMappingMetadataExtracter implements MetadataExtrac } else if (value instanceof Collection) { - Collection valueCollection = (Collection) value; + Collection valueCollection = (Collection) value; if (valueCollection.isEmpty()) { value = null; diff --git a/source/java/org/alfresco/repo/content/metadata/TikaAutoMetadataExtracterTest.java b/source/java/org/alfresco/repo/content/metadata/TikaAutoMetadataExtracterTest.java index 5a2a4bcff0..eb37a0c539 100644 --- a/source/java/org/alfresco/repo/content/metadata/TikaAutoMetadataExtracterTest.java +++ b/source/java/org/alfresco/repo/content/metadata/TikaAutoMetadataExtracterTest.java @@ -36,6 +36,7 @@ import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; +import org.apache.tika.config.TikaConfig; import org.apache.tika.metadata.Metadata; import org.apache.tika.mime.MediaType; import org.apache.tika.parser.AutoDetectParser; @@ -90,17 +91,37 @@ public class TikaAutoMetadataExtracterTest extends AbstractMetadataExtracterTest public void testSupports() throws Exception { + TikaConfig config = TikaConfig.getDefaultConfig(); + ArrayList mimeTypes = new ArrayList(); for (Parser p : new Parser[] { new OfficeParser(), new OpenDocumentParser(), new Mp3Parser(), new OOXMLParser() }) { Set mts = p.getSupportedTypes(new ParseContext()); - for (MediaType mt : mts) { - mimeTypes.add(mt.toString()); + for (MediaType mt : mts) + { + MediaType canonical = config.getMediaTypeRegistry().normalize(mt); + mimeTypes.add( canonical.toString() ); } } + // Check Tika handles it properly + AutoDetectParser p = new AutoDetectParser(); + Set amts = new HashSet(); + for (MediaType mt : p.getSupportedTypes(new ParseContext())) + { + amts.add(mt.toString()); + } + for (String mimetype : mimeTypes) + { + assertTrue( + "Tika doesn't support expected mimetype: " + mimetype, + amts.contains(mimetype) + ); + } + + // Now check the extractor does too for (String mimetype : mimeTypes) { boolean supports = extracter.isSupported(mimetype); @@ -207,7 +228,8 @@ public class TikaAutoMetadataExtracterTest extends AbstractMetadataExtracterTest assertEquals("8 8 8", p.get("Data BitsPerSample")); assertEquals("none", p.get("Transparency Alpha")); - p = openAndCheck(".bmp", "image/bmp"); + //p = openAndCheck(".bmp", "image/bmp"); // TODO Fixed in Swift, + p = openAndCheck(".bmp", "image/x-ms-bmp"); // TODO Pre-swift workaround assertEquals("409", p.get("width")); assertEquals("92", p.get("height")); assertEquals("8 8 8", p.get("Data BitsPerSample")); diff --git a/source/java/org/alfresco/repo/content/transform/EMLTransformer.java b/source/java/org/alfresco/repo/content/transform/EMLTransformer.java index 0f24bea8a5..0edee16c5e 100644 --- a/source/java/org/alfresco/repo/content/transform/EMLTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/EMLTransformer.java @@ -33,6 +33,17 @@ import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.TransformationOptions; +/** + * Uses javax.mail.MimeMessage to generate plain text versions of + * RFC822 email messages. + * Searches for all text content parts, and returns them. Any + * attachments are ignored. + * + * TIKA Note - could be replaced with the Tika email parser. Would + * require a recursing parser to be specified, but not the full + * Auto one (we don't want attachments), just one containing + * text and html related parsers. + */ public class EMLTransformer extends AbstractContentTransformer2 { public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) diff --git a/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformer.java b/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformer.java index ba99c78e81..09f90d0abe 100644 --- a/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformer.java @@ -33,6 +33,10 @@ import org.htmlparser.beans.StringBean; * @see http://htmlparser.sourceforge.net/ * @see org.htmlparser.beans.StringBean * + * Tika Note - could be convered to use the Tika HTML parser, + * but we'd potentially need a custom text handler to replicate + * the current settings around links and non-breaking spaces. + * * @author Derek Hulley */ public class HtmlParserContentTransformer extends AbstractContentTransformer2 diff --git a/source/java/org/alfresco/repo/content/transform/MailContentTransformer.java b/source/java/org/alfresco/repo/content/transform/MailContentTransformer.java index 411168e4f0..63ce505ab0 100644 --- a/source/java/org/alfresco/repo/content/transform/MailContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/MailContentTransformer.java @@ -18,179 +18,27 @@ */ package org.alfresco.repo.content.transform; -import java.io.IOException; -import java.io.InputStream; - import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.service.cmr.repository.ContentIOException; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.repository.TransformationOptions; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.poi.poifs.eventfilesystem.POIFSReader; -import org.apache.poi.poifs.eventfilesystem.POIFSReaderEvent; -import org.apache.poi.poifs.eventfilesystem.POIFSReaderListener; -import org.apache.poi.poifs.filesystem.DocumentInputStream; +import org.apache.tika.parser.Parser; +import org.apache.tika.parser.microsoft.OfficeParser; /** - * Outlook email msg format to-text transformer. + * Uses {@link http://tika.apache.org/ Apache Tika} and + * {@link http://poi.apache.org/ Apache POI} to transform + * Outlook email msg files. * - * @author Kevin Roast + * @author Nick Burch */ -public class MailContentTransformer extends AbstractContentTransformer2 +public class MailContentTransformer extends TikaPoweredContentTransformer { - private static final Log logger = LogFactory.getLog(MailContentTransformer.class); - - private static final String STREAM_PREFIX = "__substg1.0_"; - private static final int STREAM_PREFIX_LENGTH = STREAM_PREFIX.length(); - - /** - * Only support MSG to text - */ - public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) - { - if (!MimetypeMap.MIMETYPE_OUTLOOK_MSG.equals(sourceMimetype) || - !MimetypeMap.MIMETYPE_TEXT_PLAIN.equals(targetMimetype)) - { - // only support MSG -> TEXT - return false; - } - else - { - return true; - } + public MailContentTransformer() { + super(new String[] { + MimetypeMap.MIMETYPE_OUTLOOK_MSG + }); } - /** - * @see org.alfresco.repo.content.transform.AbstractContentTransformer#transformInternal(org.alfresco.service.cmr.repository.ContentReader, org.alfresco.service.cmr.repository.ContentWriter, java.util.Map) - */ @Override - protected void transformInternal(final ContentReader reader, ContentWriter writer, TransformationOptions options) - throws Exception - { - final StringBuilder sb = new StringBuilder(); - POIFSReaderListener readerListener = new POIFSReaderListener() - { - public void processPOIFSReaderEvent(final POIFSReaderEvent event) - { - try - { - if (event.getName().startsWith(STREAM_PREFIX)) - { - StreamHandler handler = new StreamHandler(event.getName(), event.getStream()); - String result = handler.process(); - if (result != null) - { - sb.append(result); - } - } - } - catch (Exception ex) - { - throw new ContentIOException("Property set stream: " + event.getPath() + event.getName(), ex); - } - } - }; - - InputStream is = null; - try - { - is = reader.getContentInputStream(); - POIFSReader poiFSReader = new POIFSReader(); - poiFSReader.registerListener(readerListener); - - try - { - poiFSReader.read(is); - } - catch (IOException err) - { - // probably not an Outlook format MSG - ignore for now - if (logger.isWarnEnabled()) - logger.warn("Unable to extract text from message: " + err.getMessage()); - } - finally - { - // Append the text to the writer - writer.putContent(sb.toString()); - } - } - finally - { - if (is != null) - { - try { is.close(); } catch (IOException e) {} - } - } - } - - private static final String ENCODING_TEXT = "001E"; - private static final String ENCODING_BINARY = "0102"; - private static final String ENCODING_UNICODE = "001F"; - - private static final String SUBSTG_MESSAGEBODY = "1000"; - - /** - * Class to handle stream types. Can process and extract specific streams. - */ - private class StreamHandler - { - StreamHandler(String name, DocumentInputStream stream) - { - this.type = name.substring(STREAM_PREFIX_LENGTH, STREAM_PREFIX_LENGTH + 4); - this.encoding = name.substring(STREAM_PREFIX_LENGTH + 4, STREAM_PREFIX_LENGTH + 8); - this.stream = stream; - } - - String process() - throws IOException - { - String result = null; - - if (SUBSTG_MESSAGEBODY.equals(this.type)) - { - result = extractText(this.encoding); - } - - return result; - } - - /** - * Extract the text from the stream based on the encoding - * - * @return String - * - * @throws IOException - */ - private String extractText(String encoding) - throws IOException - { - byte[] data = new byte[this.stream.available()]; - this.stream.read(data); - - if (encoding.equals(ENCODING_TEXT) || encoding.equals(ENCODING_BINARY)) - { - return new String(data); - } - else if (encoding.equals(ENCODING_UNICODE)) - { - // convert double-byte encoding to single byte for String conversion - byte[] b = new byte[data.length >> 1]; - for (int i=0; i copyProperties = new HashMap(properties); for (CopyBehaviourCallback callback : callbacks) { - copyProperties = callback.getCopyProperties(classQName, copyDetails, properties); + Map propsToCopy = callback.getCopyProperties(classQName, + copyDetails, + copyProperties); + + if(propsToCopy != copyProperties) + { + /* + * Collections.emptyMap() is a valid return from the callback so we need to ensure it + * is still mutable for the next iteration + */ + copyProperties = new HashMap(propsToCopy); + } } // Done if (logger.isDebugEnabled()) diff --git a/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java b/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java index 0f31befff1..65eba4d981 100644 --- a/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java +++ b/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java @@ -724,8 +724,6 @@ public class CopyServiceImplTest extends BaseSpringTest // Make node one actionable with a rule to copy nodes into node two Map params = new HashMap(1); params.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, nodeTwo); - params.put(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, TEST_CHILD_ASSOC_TYPE_QNAME); - params.put(MoveActionExecuter.PARAM_ASSOC_QNAME, QName.createQName("{test}ruleCopy")); Rule rule = new Rule(); rule.setRuleType(RuleType.INBOUND); Action action = this.actionService.createAction(CopyActionExecuter.NAME, params); diff --git a/source/java/org/alfresco/repo/copy/CopyServicePolicies.java b/source/java/org/alfresco/repo/copy/CopyServicePolicies.java index 367ba7ebbe..2f1eb0ac66 100644 --- a/source/java/org/alfresco/repo/copy/CopyServicePolicies.java +++ b/source/java/org/alfresco/repo/copy/CopyServicePolicies.java @@ -106,8 +106,7 @@ public interface CopyServicePolicies * @param classRef the type or aspect qualified name * @param copyDetails the details of the impending copy * @return Return the callback that will be used to modify the copy behaviour for this - * dictionary class. Return null to assume the default copy the helper to carry information back to the Copy Service. If this is not used, then - * neither the aspect nor any of its properties will be copied. + * dictionary class. Return null to assume the default. * * @see CopyServicePolicies * diff --git a/source/java/org/alfresco/repo/dictionary/RepoDictionaryDAOTest.java b/source/java/org/alfresco/repo/dictionary/RepoDictionaryDAOTest.java index 0e61509b6e..8ade0690a5 100644 --- a/source/java/org/alfresco/repo/dictionary/RepoDictionaryDAOTest.java +++ b/source/java/org/alfresco/repo/dictionary/RepoDictionaryDAOTest.java @@ -201,7 +201,7 @@ public class RepoDictionaryDAOTest extends TestCase QName model = QName.createQName(TEST_URL, "dictionarydaotest"); Collection modelConstraints = service.getConstraints(model); - assertEquals(15, modelConstraints.size()); // 8 + 7 + assertEquals(20, modelConstraints.size()); // 9 + 11 QName conRegExp1QName = QName.createQName(TEST_URL, "regex1"); boolean found1 = false; diff --git a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java index 3519ecd6b5..22506ac1c7 100644 --- a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java @@ -53,9 +53,14 @@ import org.alfresco.repo.domain.usage.UsageDAO; import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.security.permissions.AccessControlListProperties; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.TransactionAwareSingleton; import org.alfresco.repo.transaction.TransactionListenerAdapter; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.repo.transaction.TransactionalResourceHelper; +import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.InvalidTypeException; @@ -69,18 +74,19 @@ import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.InvalidStoreRefException; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeRef.Status; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.repository.NodeRef.Status; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.ReadOnlyServerException; +import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.EqualsHelper; +import org.alfresco.util.EqualsHelper.MapValueComparison; import org.alfresco.util.GUID; import org.alfresco.util.Pair; import org.alfresco.util.PropertyCheck; import org.alfresco.util.ReadWriteLockExecuter; import org.alfresco.util.SerializationUtils; -import org.alfresco.util.EqualsHelper.MapValueComparison; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.dao.ConcurrencyFailureException; @@ -92,8 +98,6 @@ import org.springframework.util.Assert; *

* This provides basic services such as caching, but defers to the underlying implementation * for CRUD operations. - *

- * TODO: Timestamp propagation * * @author Derek Hulley * @since 3.4 @@ -113,8 +117,12 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO private NodePropertyHelper nodePropertyHelper; private ServerIdCallback serverIdCallback = new ServerIdCallback(); private UpdateTransactionListener updateTransactionListener = new UpdateTransactionListener(); + private AuditableTransactionListener auditableTransactionListener = new AuditableTransactionListener(); private RetryingCallbackHelper childAssocRetryingHelper; + + private boolean enableTimestampPropagation; + private TransactionService transactionService; private DictionaryService dictionaryService; private BehaviourFilter policyBehaviourFilter; private AclDAO aclDAO; @@ -177,6 +185,26 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO parentAssocsCache = new EntityLookupCache(new ParentAssocsCallbackDAO()); } + /** + * Set whether cm:auditable timestamps should be propagated to parent nodes + * where the parent-child relationship has been marked using propagateTimestamps. + * + * @param enableTimestampPropagation true to propagate timestamps to the parent + * node where appropriate + */ + public void setEnableTimestampPropagation(boolean enableTimestampPropagation) + { + this.enableTimestampPropagation = enableTimestampPropagation; + } + + /** + * @param transactionService the service to start post-txn processes + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + /** * @param dictionaryService the service help determine cm:auditable characteristics */ @@ -322,6 +350,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO public void init() { + PropertyCheck.mandatory(this, "transactionService", transactionService); PropertyCheck.mandatory(this, "dictionaryService", dictionaryService); PropertyCheck.mandatory(this, "aclDAO", aclDAO); PropertyCheck.mandatory(this, "accessControlListDAO", accessControlListDAO); @@ -894,6 +923,12 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO ParentAssocsInfo parentAssocsInfo = new ParentAssocsInfo(isRoot, isStoreRoot, assoc); parentAssocsCache.setValue(nodeId, parentAssocsInfo); + // Ensure that cm:auditable values are propagated, if required + if (enableTimestampPropagation) + { + propagateTimestamps(nodeId); + } + if (isDebugEnabled) { logger.debug( @@ -1037,6 +1072,9 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO else { oldParentNodeId = primaryParentAssoc.getParentNode().getId(); + + // Update the parent node, if required + propagateTimestamps(childNodeId); } } @@ -1402,6 +1440,14 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO newNodeImpl(oldStore, oldUuid, ContentModel.TYPE_CMOBJECT, null, true, null); } + // Ensure that cm:auditable values are propagated, if required + if (enableTimestampPropagation && + nodeUpdate.isUpdateAuditableProperties() && + nodeUpdate.getAuditableProperties() != null) + { + propagateTimestamps(nodeId); + } + // Update the caches nodeUpdate.lock(); nodesCache.setValue(nodeId, nodeUpdate); @@ -1420,7 +1466,133 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO " NEW: " + nodeUpdate); } } - + + private static final String KEY_AUDITABLE_PROPAGATION = "node.auditable.propagation"; + private static final String KEY_AUDITABLE_PROPAGATION_DISABLE = "node.auditable.propagation.disable"; + /** + * Schedule auditable property propagation for the post-commit phase + * + * @param childNodeId the ID of the node that has auditable properties changed + */ + private void propagateTimestamps(Long childNodeId) + { + if (!enableTimestampPropagation) + { + return; // Don't propagate + } + // Get the current timestamp + Node childNode = getNodeNotNull(childNodeId); + if (childNode.getAuditableProperties() == null) + { + return; // Not auditable + } + String modified = childNode.getAuditableProperties().getAuditModified(); + // Check the parent association + ChildAssocEntity primaryParentAssoc = getPrimaryParentAssocImpl(childNodeId); + if (primaryParentAssoc == null) + { + return; // This is a root + } + // Check the association type + Long assocTypeQNameId = primaryParentAssoc.getTypeQNameId(); + Pair assocTypeQNamePair = qnameDAO.getQName(assocTypeQNameId); + if (assocTypeQNamePair == null) + { + return; // Unknown association type + } + AssociationDefinition assocDef = dictionaryService.getAssociation(assocTypeQNamePair.getSecond()); + if (!assocDef.isChild() || !((ChildAssociationDefinition)assocDef).getPropagateTimestamps()) + { + return; // Don't propagate + } + + // Record the parent node ID for update + Long parentNodeId = primaryParentAssoc.getParentNode().getId(); + Map modifiedDatesById = TransactionalResourceHelper.getMap(KEY_AUDITABLE_PROPAGATION); + String existingModified = modifiedDatesById.get(parentNodeId); + if (existingModified != null && existingModified.compareTo(modified) > 0) + { + return; // Already have a later date ready to go + } + modifiedDatesById.put(parentNodeId, modified); + + // Bind a listener for post-transaction manipulation + AlfrescoTransactionSupport.bindListener(auditableTransactionListener); + } + + /** + * Wrapper to update the current transaction to get the change time correct + * + * @author Derek Hulley + * @since 3.4.2 + */ + private class AuditableTransactionListener extends TransactionListenerAdapter + { + @Override + public void afterCommit() + { + // Check if we are already propagating + if (AlfrescoTransactionSupport.getResource(KEY_AUDITABLE_PROPAGATION_DISABLE) != null) + { + // This is a propagating transaction, so do nothing + return; + } + + Map modifiedDatesById = TransactionalResourceHelper.getMap(KEY_AUDITABLE_PROPAGATION); + if (modifiedDatesById.size() == 0) + { + return; + } + // Walk through the IDs, processing groups + for (Map.Entry entry: modifiedDatesById.entrySet()) + { + Long parentNodeId = entry.getKey(); + String modified = entry.getValue(); + processBatch(parentNodeId, modified); + } + } + + private void processBatch(final Long parentNodeId, final String modified) + { + RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); + txnHelper.setMaxRetries(1); + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Disable all behaviour. + // This only affects cm:auditable and is discarded at the end of this txn + policyBehaviourFilter.disableAllBehaviours(); + // Tag the transaction to prevent further propagation + AlfrescoTransactionSupport.bindResource(KEY_AUDITABLE_PROPAGATION_DISABLE, Boolean.TRUE); + + Pair parentNodePair = getNodePair(parentNodeId); + if (parentNodePair == null) + { + return null; // Parent has gone away + } + // Modify the parent with the new date (if it needs) + // Disable cm:auditable for the parent so that we can set it manually + addNodeProperty(parentNodeId, ContentModel.PROP_MODIFIED, modified); + return null; + } + }; + try + { + txnHelper.doInTransaction(callback, false, true); + if (isDebugEnabled) + { + logger.debug("Propagated timestamps from node: " + parentNodeId); + } + } + catch (Throwable e) + { + logger.info("Failed to update auditable properties for nodes: " + parentNodeId); + } + } + } + public void setNodeAclId(Long nodeId, Long aclId) { Node oldNode = getNodeNotNull(nodeId); @@ -1453,6 +1625,12 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO public void deleteNode(Long nodeId) { + // Ensure that cm:auditable values are propagated, if required + if (enableTimestampPropagation) + { + propagateTimestamps(nodeId); + } + Node node = getNodeNotNull(nodeId); Long aclId = node.getAclId(); // Need this later diff --git a/source/java/org/alfresco/repo/domain/patch/AbstractPatchDAOImpl.java b/source/java/org/alfresco/repo/domain/patch/AbstractPatchDAOImpl.java index 1305fa4a81..11f98c2d78 100644 --- a/source/java/org/alfresco/repo/domain/patch/AbstractPatchDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/patch/AbstractPatchDAOImpl.java @@ -262,4 +262,5 @@ public abstract class AbstractPatchDAOImpl implements PatchDAO, BatchingDAO } protected abstract void deleteAllOldAttrsImpl(); + } diff --git a/source/java/org/alfresco/repo/domain/patch/PatchDAO.java b/source/java/org/alfresco/repo/domain/patch/PatchDAO.java index 526609c1b7..f3ddc37511 100644 --- a/source/java/org/alfresco/repo/domain/patch/PatchDAO.java +++ b/source/java/org/alfresco/repo/domain/patch/PatchDAO.java @@ -213,4 +213,36 @@ public interface PatchDAO * Delete all old attributes (from alf_*attribute* tables) */ public void deleteAllOldAttrs(); + + /** + * Get shared acls with inheritance issues + * @return + */ + public List> getSharedAclsThatDoNotInheritCorrectlyFromThePrimaryParent(); + + /** + * Get defining acls with inheritance issues + * @return + */ + public List> getDefiningAclsThatDoNotInheritCorrectlyFromThePrimaryParent(); + + /** + * Get acls that do not inherit from the primary parent. + * @return + */ + public List> getAclsThatInheritFromNonPrimaryParent(); + + /** + * Get acls that inherit with inheritance unset + * @return + */ + public List> getAclsThatInheritWithInheritanceUnset(); + + /** + * Get shared acls that do not inherit correctly from the defining acl + * @return + */ + public List> getSharedAclsThatDoNotInheritCorrectlyFromTheirDefiningAcl(); + + } diff --git a/source/java/org/alfresco/repo/domain/patch/ibatis/PatchDAOImpl.java b/source/java/org/alfresco/repo/domain/patch/ibatis/PatchDAOImpl.java index 6d69a41b8c..b5851819e0 100644 --- a/source/java/org/alfresco/repo/domain/patch/ibatis/PatchDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/patch/ibatis/PatchDAOImpl.java @@ -94,7 +94,13 @@ public class PatchDAOImpl extends AbstractPatchDAOImpl private static final String DELETE_OLD_ATTR_MAP = "alfresco.patch.delete_oldAttrAlfMapAttributeEntries"; private static final String DELETE_OLD_ATTR_GLOBAL = "alfresco.patch.delete_oldAttrAlfGlobalAttributes"; private static final String DELETE_OLD_ATTR = "alfresco.patch.delete_oldAttrAlfAttributes"; - + + private static final String SELECT_ACLS_THAT_INHERIT_FROM_NON_PRIMARY_PARENT = "alfresco.patch.select_aclsThatInheritFromNonPrimaryParent"; + private static final String SELECT_ACLS_THAT_INHERIT_WITH_INHERITANCE_UNSET = "alfresco.patch.select_aclsThatInheritWithInheritanceUnset"; + private static final String SELECT_DEFINING_ACLS_THAT_DO_NOT_INHERIT_CORRECTLY_FROM_THE_PRIMARY_PARENT = "alfresco.patch.select_definingAclsThatDoNotInheritCorrectlyFromThePrimaryParent"; + private static final String SELECT_SHARED_ACLS_THAT_DO_NOT_INHERIT_CORRECTLY_FROM_THE_PRIMARY_PARENT = "alfresco.patch.select_sharedAclsThatDoNotInheritCorrectlyFromThePrimaryParent"; + private static final String SELECT_SHARED_ACLS_THAT_DO_NOT_INHERIT_CORRECTLY_FROM_THEIR_DEFINING_ACL = "alfresco.patch.select_sharedAclsThatDoNotInheritCorrectlyFromTheirDefiningAcl"; + private SqlMapClientTemplate template; private QNameDAO qnameDAO; private LocaleDAO localeDAO; @@ -534,4 +540,54 @@ public class PatchDAOImpl extends AbstractPatchDAOImpl deleted = template.delete(DELETE_OLD_ATTR); logger.info("Deleted "+deleted+" rows from alf_attributes"); } + + @SuppressWarnings("unchecked") + @Override + public List> getAclsThatInheritFromNonPrimaryParent() + { + List> rows = template.queryForList( + SELECT_ACLS_THAT_INHERIT_FROM_NON_PRIMARY_PARENT, + Boolean.TRUE); + return rows; + } + + @SuppressWarnings("unchecked") + @Override + public List> getAclsThatInheritWithInheritanceUnset() + { + List> rows = template.queryForList( + SELECT_ACLS_THAT_INHERIT_WITH_INHERITANCE_UNSET, + Boolean.TRUE); + return rows; + } + + @SuppressWarnings("unchecked") + @Override + public List> getDefiningAclsThatDoNotInheritCorrectlyFromThePrimaryParent() + { + List> rows = template.queryForList( + SELECT_DEFINING_ACLS_THAT_DO_NOT_INHERIT_CORRECTLY_FROM_THE_PRIMARY_PARENT, + Boolean.TRUE); + return rows; + } + + @SuppressWarnings("unchecked") + @Override + public List> getSharedAclsThatDoNotInheritCorrectlyFromThePrimaryParent() + { + List> rows = template.queryForList( + SELECT_SHARED_ACLS_THAT_DO_NOT_INHERIT_CORRECTLY_FROM_THE_PRIMARY_PARENT, + Boolean.TRUE); + return rows; + } + + @SuppressWarnings("unchecked") + @Override + public List> getSharedAclsThatDoNotInheritCorrectlyFromTheirDefiningAcl() + { + List> rows = template.queryForList( + SELECT_SHARED_ACLS_THAT_DO_NOT_INHERIT_CORRECTLY_FROM_THEIR_DEFINING_ACL, + Boolean.TRUE); + return rows; + } } diff --git a/source/java/org/alfresco/repo/domain/permissions/AVMAccessControlListDAO.java b/source/java/org/alfresco/repo/domain/permissions/AVMAccessControlListDAO.java index 5dd1faad35..810e06f7f3 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AVMAccessControlListDAO.java +++ b/source/java/org/alfresco/repo/domain/permissions/AVMAccessControlListDAO.java @@ -1203,4 +1203,14 @@ public class AVMAccessControlListDAO implements AccessControlListDAO throw new UnsupportedOperationException(); } + /* (non-Javadoc) + * @see org.alfresco.repo.domain.permissions.AccessControlListDAO#setFixedAcls(java.lang.Long, java.lang.Long, java.lang.Long, java.lang.Long, java.util.List, boolean) + */ + @Override + public void setFixedAcls(Long nodeId, Long inheritFrom, Long mergeFrom, Long sharedAclToReplace, List changes, boolean set) + { + throw new UnsupportedOperationException(); + + } + } diff --git a/source/java/org/alfresco/repo/domain/permissions/AccessControlListDAO.java b/source/java/org/alfresco/repo/domain/permissions/AccessControlListDAO.java index a78091951f..68b238610e 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AccessControlListDAO.java +++ b/source/java/org/alfresco/repo/domain/permissions/AccessControlListDAO.java @@ -90,4 +90,6 @@ public interface AccessControlListDAO public void setAccessControlList(StoreRef storeRef, Acl acl); public void updateInheritance(Long childNodeId, Long oldParentNodeId, Long newParentNodeId); + + public void setFixedAcls(Long nodeId, Long inheritFrom, Long mergeFrom, Long sharedAclToReplace, List changes, boolean set); } diff --git a/source/java/org/alfresco/repo/domain/permissions/AclDAO.java b/source/java/org/alfresco/repo/domain/permissions/AclDAO.java index 8f5c0ae2bd..52ff222c69 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AclDAO.java +++ b/source/java/org/alfresco/repo/domain/permissions/AclDAO.java @@ -154,4 +154,10 @@ public interface AclDAO public void renameAuthority(String before, String after); public void deleteAclForNode(long aclId, boolean isAVMNode); + + /** + * @param inheritedAclId + * @param aclId + */ + public void fixSharedAcl(Long shared, Long defining); } diff --git a/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java b/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java index 7486948f96..877f6da243 100644 --- a/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/permissions/AclDAOImpl.java @@ -65,13 +65,13 @@ import org.apache.commons.logging.LogFactory; public class AclDAOImpl implements AclDAO { private static Log logger = LogFactory.getLog(AclDAOImpl.class); - + /** Access to QName entities */ private QNameDAO qnameDAO; - + /** Access to ACL entities */ private AclCrudDAO aclCrudDAO; - + /** Access to Nodes entities */ private NodeDAO nodeDAO; @@ -79,7 +79,7 @@ public class AclDAOImpl implements AclDAO /** a transactionally-safe cache to be injected */ private SimpleCache aclCache; - + private SimpleCache> readersCache; private enum WriteMode @@ -113,12 +113,12 @@ public class AclDAOImpl implements AclDAO */ COPY_ONLY, CREATE_AND_INHERIT; } - + public void setQnameDAO(QNameDAO qnameDAO) { this.qnameDAO = qnameDAO; } - + public void setTenantService(TenantService tenantService) { this.tenantService = tenantService; @@ -128,7 +128,7 @@ public class AclDAOImpl implements AclDAO { this.aclCrudDAO = aclCrudDAO; } - + public void setNodeDAO(NodeDAO nodeDAO) { this.nodeDAO = nodeDAO; @@ -159,7 +159,7 @@ public class AclDAOImpl implements AclDAO { return createAccessControlList(getDefaultProperties()).getId(); } - + /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#getDefaultProperties() */ @@ -171,7 +171,7 @@ public class AclDAOImpl implements AclDAO properties.setVersioned(false); return properties; } - + /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#createAcl(org.alfresco.repo.security.permissions.AccessControlListProperties) */ @@ -181,7 +181,7 @@ public class AclDAOImpl implements AclDAO { throw new IllegalArgumentException("Properties cannot be null"); } - + if (properties.getAclType() == null) { throw new IllegalArgumentException("ACL Type must be defined"); @@ -214,7 +214,7 @@ public class AclDAOImpl implements AclDAO } return createAccessControlList(properties, null, null); } - + /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#createAcl(org.alfresco.repo.security.permissions.AccessControlListProperties, java.util.List, java.lang.Long) */ @@ -224,7 +224,7 @@ public class AclDAOImpl implements AclDAO { throw new IllegalArgumentException("Properties cannot be null"); } - + AclEntity acl = new AclEntity(); if (properties.getAclId() != null) { @@ -236,7 +236,7 @@ public class AclDAOImpl implements AclDAO } acl.setAclType(properties.getAclType()); acl.setAclVersion(Long.valueOf(1l)); - + switch (properties.getAclType()) { case FIXED: @@ -258,7 +258,7 @@ public class AclDAOImpl implements AclDAO break; } acl.setLatest(Boolean.TRUE); - + switch (properties.getAclType()) { case OLD: @@ -289,82 +289,90 @@ public class AclDAOImpl implements AclDAO } break; } - + acl.setAclChangeSetId(getCurrentChangeSetId()); acl.setRequiresVersion(false); - + Acl createdAcl = (AclEntity)aclCrudDAO.createAcl(acl); long created = createdAcl.getId(); - + + List toAdd = new ArrayList(); + List excluded = new ArrayList(); + List changes = new ArrayList(); if ((aces != null) && aces.size() > 0) { - List changes = new ArrayList(); - - List toAdd = new ArrayList(aces.size()); - List excluded = new ArrayList(aces.size()); for (AccessControlEntry ace : aces) { if ((ace.getPosition() != null) && (ace.getPosition() != 0)) { throw new IllegalArgumentException("Invalid position"); } - + // Find authority Authority authority = aclCrudDAO.getOrCreateAuthority(ace.getAuthority()); Permission permission = aclCrudDAO.getOrCreatePermission(ace.getPermission()); - + // Find context if (ace.getContext() != null) { throw new UnsupportedOperationException(); } - + // Find ACE Ace entry = aclCrudDAO.getOrCreateAce(permission, authority, ace.getAceType(), ace.getAccessStatus()); - + // Wire up // COW and remove any existing matches - + SimpleAccessControlEntry exclude = new SimpleAccessControlEntry(); // match any access status exclude.setAceType(ace.getAceType()); exclude.setAuthority(ace.getAuthority()); exclude.setPermission(ace.getPermission()); exclude.setPosition(0); - + toAdd.add(entry); excluded.add(exclude); // Will remove from the cache } - Long toInherit = null; - if (inherited != null) - { - toInherit = getInheritedAccessControlList(inherited); - } - getWritable(created, toInherit, excluded, toAdd, toInherit, false, changes, WriteMode.CREATE_AND_INHERIT); } - + Long toInherit = null; + if (inherited != null) + { + toInherit = getInheritedAccessControlList(inherited); + } + getWritable(created, toInherit, excluded, toAdd, toInherit, false, changes, WriteMode.CREATE_AND_INHERIT); + + return createdAcl; } - + private void getWritable(final Long id, final Long parent, List exclude, List toAdd, Long inheritsFrom, boolean cascade, List changes, WriteMode mode) { List inherited = null; List positions = null; - - if ((mode == WriteMode.ADD_INHERITED) || (mode == WriteMode.INSERT_INHERITED) || (mode == WriteMode.CHANGE_INHERITED)) + + if ((mode == WriteMode.ADD_INHERITED) || (mode == WriteMode.INSERT_INHERITED) || (mode == WriteMode.CHANGE_INHERITED) || (mode == WriteMode.CREATE_AND_INHERIT )) { inherited = new ArrayList(); positions = new ArrayList(); - + // get aces for acl (via acl member) - List members = aclCrudDAO.getAclMembersByAcl(parent); - + List members; + if(parent != null) + { + members = aclCrudDAO.getAclMembersByAcl(parent); + } + else + { + members = Collections.emptyList(); + } + for (AclMember member : members) { Ace aceEntity = aclCrudDAO.getAce(member.getAceId()); - + if ((mode == WriteMode.INSERT_INHERITED) && (member.getPos() == 0)) { inherited.add(aceEntity); @@ -377,10 +385,10 @@ public class AclDAOImpl implements AclDAO } } } - + getWritable(id, parent, exclude, toAdd, inheritsFrom, inherited, positions, cascade, 0, changes, mode, false); } - + /** * Make a whole tree of ACLs copy on write if required Includes adding and removing ACEs which can be optimised * slightly for copy on write (no need to add and then remove) @@ -399,27 +407,27 @@ public class AclDAOImpl implements AclDAO { AclChange current = getWritable(id, parent, exclude, toAdd, inheritsFrom, inherited, positions, depth, mode, requiresVersion); changes.add(current); - + boolean cascadeVersion = requiresVersion; if (!cascadeVersion) { cascadeVersion = !current.getBefore().equals(current.getAfter()); } - + if (cascade) { List inheritors = aclCrudDAO.getAclsThatInheritFromAcl(id); for (Long nextId : inheritors) { // Check for those that inherit themselves to other nodes ... - if (nextId != id) + if (!nextId.equals(id)) { getWritable(nextId, current.getAfter(), exclude, toAdd, current.getAfter(), inherited, positions, cascade, depth + 1, changes, mode, cascadeVersion); } } } } - + /** * COW for an individual ACL * @@ -441,7 +449,7 @@ public class AclDAOImpl implements AclDAO readersCache.remove(id); return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType()); } - + List toAdd = new ArrayList(0); if (acesToAdd != null) { @@ -450,7 +458,7 @@ public class AclDAOImpl implements AclDAO toAdd.add(ace.getId()); } } - + if (!acl.isVersioned()) { switch (mode) @@ -484,8 +492,8 @@ public class AclDAOImpl implements AclDAO if (inheritsFrom != null) { acl.setInheritsFrom(inheritsFrom); - aclCrudDAO.updateAcl(acl); } + aclCrudDAO.updateAcl(acl); aclCache.remove(id); readersCache.remove(id); return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType()); @@ -523,8 +531,8 @@ public class AclDAOImpl implements AclDAO if (inheritsFrom != null) { acl.setInheritsFrom(inheritsFrom); - aclCrudDAO.updateAcl(acl); } + aclCrudDAO.updateAcl(acl); aclCache.remove(id); readersCache.remove(id); return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType()); @@ -542,32 +550,32 @@ public class AclDAOImpl implements AclDAO newAcl.setLatest(Boolean.TRUE); newAcl.setVersioned(Boolean.TRUE); newAcl.setRequiresVersion(Boolean.FALSE); - + AclEntity createdAcl = (AclEntity)aclCrudDAO.createAcl(newAcl); long created = createdAcl.getId(); - + // Create new membership entries - excluding those in the given pattern - + // AcePatternMatcher excluder = new AcePatternMatcher(exclude); - + // get aces for acl (via acl member) List members = aclCrudDAO.getAclMembersByAcl(id); - + if (members.size() > 0) { List> aceIdsWithDepths = new ArrayList>(members.size()); - + for (AclMember member : members) { aceIdsWithDepths.add(new Pair(member.getAceId(), member.getPos())); } - + // copy acl members to new acl aclCrudDAO.addAclMembersToAcl(newAcl.getId(), aceIdsWithDepths); } - + // add new - + switch (mode) { case COPY_UPDATE_AND_INHERIT: @@ -597,7 +605,7 @@ public class AclDAOImpl implements AclDAO default: break; } - + // Fix up inherited ACL if required if (newAcl.getAclType() == ACLType.SHARED) { @@ -609,7 +617,7 @@ public class AclDAOImpl implements AclDAO aclCrudDAO.updateAcl(parentAcl); } } - + // fix up old version acl.setLatest(Boolean.FALSE); acl.setRequiresVersion(Boolean.FALSE); @@ -619,7 +627,7 @@ public class AclDAOImpl implements AclDAO return new AclChangeImpl(id, created, acl.getAclType(), newAcl.getAclType()); } } - + /** * Helper to remove ACEs from an ACL * @@ -637,35 +645,35 @@ public class AclDAOImpl implements AclDAO else { AcePatternMatcher excluder = new AcePatternMatcher(exclude); - + List> results = aclCrudDAO.getAcesAndAuthoritiesByAcl(id); List memberIds = new ArrayList(results.size()); - + for (Map result : results) { Long result_aclmemId = (Long) result.get("aclmemId"); - + if ((exclude != null) && excluder.matches(aclCrudDAO, result, depth)) { memberIds.add(result_aclmemId); } } - + // delete list of acl members aclCrudDAO.deleteAclMembers(memberIds); } } - + private void replaceInherited(Long id, Acl acl, List inherited, List positions, int depth) { truncateInherited(id, depth); addInherited(acl, inherited, positions, depth); } - + private void truncateInherited(final Long id, int depth) { List members = aclCrudDAO.getAclMembersByAcl(id); - + List membersToDelete = new ArrayList(members.size()); for (AclMember member : members) { @@ -674,18 +682,18 @@ public class AclDAOImpl implements AclDAO membersToDelete.add(member.getId()); } } - + if (membersToDelete.size() > 0) { // delete list of acl members aclCrudDAO.deleteAclMembers(membersToDelete); } } - + private void removeInherited(final Long id, int depth) { List members = aclCrudDAO.getAclMembersByAclForUpdate(id); - + List membersToDelete = new ArrayList(members.size()); for (AclMemberEntity member : members) { @@ -699,14 +707,14 @@ public class AclDAOImpl implements AclDAO aclCrudDAO.updateAclMember(member); } } - + if (membersToDelete.size() > 0) { // delete list of acl members aclCrudDAO.deleteAclMembers(membersToDelete); } } - + private void addInherited(Acl acl, List inherited, List positions, int depth) { if ((inherited != null) && (inherited.size() > 0)) @@ -721,12 +729,12 @@ public class AclDAOImpl implements AclDAO aclCrudDAO.addAclMembersToAcl(acl.getId(), aceIdsWithDepths); } } - + private void insertInherited(final Long id, AclEntity acl, List inherited, List positions, int depth) { // get aces for acl (via acl member) List members = aclCrudDAO.getAclMembersByAclForUpdate(id); - + for (AclMemberEntity member : members) { if (member.getPos() > depth) @@ -735,33 +743,33 @@ public class AclDAOImpl implements AclDAO aclCrudDAO.updateAclMember(member); } } - + addInherited(acl, inherited, positions, depth); } - + /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#deleteAccessControlEntries(java.lang.String) */ public List deleteAccessControlEntries(final String authority) { List acls = new ArrayList(); - + // get authority Authority authEntity = aclCrudDAO.getAuthority(authority); if (authEntity == null) { return acls; } - + List aces = new ArrayList(); - + List members = aclCrudDAO.getAclMembersByAuthority(authority); - + boolean leaveAuthority = false; if (members.size() > 0) { List membersToDelete = new ArrayList(members.size()); - + // fix up members and extract acls and aces for (AclMember member : members) { @@ -769,16 +777,16 @@ public class AclDAOImpl implements AclDAO Long aclMemberId = member.getId(); Long aclId = member.getAclId(); Long aceId = member.getAceId(); - + boolean hasAnotherTenantNodes = false; if (AuthenticationUtil.isMtEnabled()) { // ALF-3563 - + // Retrieve dependent nodes List nodeIds = aclCrudDAO.getADMNodesByAcl(aclId, -1); nodeIds.addAll(aclCrudDAO.getAVMNodesByAcl(aclId, -1)); - + if (nodeIds.size() > 0) { for (Long nodeId : nodeIds) @@ -789,7 +797,7 @@ public class AclDAOImpl implements AclDAO logger.warn("Node does not exist: " + nodeId); } NodeRef nodeRef = nodePair.getSecond(); - + try { // Throws AlfrescoRuntimeException in case of domain mismatch @@ -804,33 +812,33 @@ public class AclDAOImpl implements AclDAO } } } - + if (!hasAnotherTenantNodes) { aclCache.remove(aclId); readersCache.remove(aclId); - + Acl list = aclCrudDAO.getAcl(aclId); acls.add(new AclChangeImpl(aclId, aclId, list.getAclType(), list.getAclType())); membersToDelete.add(aclMemberId); aces.add((Long)aceId); } } - + // delete list of acl members aclCrudDAO.deleteAclMembers(membersToDelete); } - + if (!leaveAuthority) { // remove ACEs aclCrudDAO.deleteAces(aces); - + // Tidy up any unreferenced ACEs - + // get aces by authority List unreferenced = aclCrudDAO.getAcesByAuthority(authEntity.getId()); - + if (unreferenced.size() > 0) { List unrefencedAcesToDelete = new ArrayList(unreferenced.size()); @@ -840,17 +848,17 @@ public class AclDAOImpl implements AclDAO } aclCrudDAO.deleteAces(unrefencedAcesToDelete); } - + // remove authority if (authEntity != null) { aclCrudDAO.deleteAuthority(authEntity.getId()); } } - + return acls; } - + /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#deleteAclForNode(long, boolean) */ @@ -862,7 +870,7 @@ public class AclDAOImpl implements AclDAO // delete acl members & acl aclCrudDAO.deleteAclMembersByAcl(aclId); aclCrudDAO.deleteAcl(aclId); - + aclCache.remove(aclId); readersCache.remove(aclId); } @@ -880,7 +888,7 @@ public class AclDAOImpl implements AclDAO // delete acl members & acl aclCrudDAO.deleteAclMembersByAcl(aclId); aclCrudDAO.deleteAcl(aclId); - + aclCache.remove(aclId); readersCache.remove(aclId); } @@ -892,7 +900,7 @@ public class AclDAOImpl implements AclDAO } } } - + /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#deleteAccessControlList(java.lang.Long) */ @@ -903,15 +911,15 @@ public class AclDAOImpl implements AclDAO // debug only int maxForDebug = 11; List nodeIds = getADMNodesByAcl(id, maxForDebug); - + for (Long nodeId : nodeIds) { logger.debug("deleteAccessControlList: Found nodeId=" + nodeId + ", aclId=" + id); } } - + List acls = new ArrayList(); - + final AclUpdateEntity acl = aclCrudDAO.getAclForUpdate(id); if (!acl.isLatest()) { @@ -921,13 +929,13 @@ public class AclDAOImpl implements AclDAO { throw new UnsupportedOperationException("Delete is not supported for shared acls - they are deleted with the defining acl"); } - + if ((acl.getAclType() == ACLType.DEFINING) || (acl.getAclType() == ACLType.LAYERED)) { if ((acl.getInheritedAcl() != null) && (acl.getInheritedAcl() != -1)) { final Acl inherited = aclCrudDAO.getAcl(acl.getInheritedAcl()); - + // Will remove from the cache getWritable(inherited.getId(), acl.getInheritsFrom(), null, null, null, true, acls, WriteMode.REMOVE_INHERITED); Acl unusedInherited = null; @@ -938,7 +946,7 @@ public class AclDAOImpl implements AclDAO unusedInherited = aclCrudDAO.getAcl(change.getAfter()); } } - + final Long newId = unusedInherited.getId(); List inheritors = aclCrudDAO.getAclsThatInheritFromAcl(newId); for (Long nextId : inheritors) @@ -946,13 +954,13 @@ public class AclDAOImpl implements AclDAO // Will remove from the cache getWritable(nextId, acl.getInheritsFrom(), null, null, acl.getInheritsFrom(), true, acls, WriteMode.REMOVE_INHERITED); } - + // delete acl members aclCrudDAO.deleteAclMembersByAcl(newId); - + // delete 'unusedInherited' acl aclCrudDAO.deleteAcl(unusedInherited.getId()); - + if (inherited.isVersioned()) { AclUpdateEntity inheritedForUpdate = aclCrudDAO.getAclForUpdate(inherited.getId()); @@ -978,7 +986,7 @@ public class AclDAOImpl implements AclDAO getWritable(nextId, acl.getInheritsFrom(), null, null, null, true, acls, WriteMode.REMOVE_INHERITED); } } - + // delete if (acl.isVersioned()) { @@ -991,14 +999,14 @@ public class AclDAOImpl implements AclDAO aclCrudDAO.deleteAclMembersByAcl(id); aclCrudDAO.deleteAcl(acl.getId()); } - + // remove the deleted acl from the cache aclCache.remove(id); readersCache.remove(id); acls.add(new AclChangeImpl(id, null, acl.getAclType(), null)); return acls; } - + /** * {@inheritDoc} */ @@ -1011,7 +1019,7 @@ public class AclDAOImpl implements AclDAO getWritable(id, null, Collections.singletonList(pattern), null, null, true, changes, WriteMode.COPY_UPDATE_AND_INHERIT); return changes; } - + /** * {@inheritDoc} */ @@ -1024,7 +1032,7 @@ public class AclDAOImpl implements AclDAO getWritable(id, null, Collections.singletonList(pattern), null, null, true, changes, WriteMode.COPY_UPDATE_AND_INHERIT); return changes; } - + /** * {@inheritDoc} */ @@ -1035,7 +1043,7 @@ public class AclDAOImpl implements AclDAO getWritable(id, null, Collections.singletonList(pattern), null, null, true, changes, WriteMode.COPY_UPDATE_AND_INHERIT); return changes; } - + /** * {@inheritDoc} */ @@ -1043,7 +1051,7 @@ public class AclDAOImpl implements AclDAO { return aclCrudDAO.getAcl(id); } - + /** * {@inheritDoc} */ @@ -1052,7 +1060,7 @@ public class AclDAOImpl implements AclDAO ParameterCheck.mandatory("id", id); // Prevent unboxing failures return aclCrudDAO.getAcl(id); } - + /** * {@inheritDoc} */ @@ -1070,7 +1078,7 @@ public class AclDAOImpl implements AclDAO } return acl; } - + /** * @return the access control list */ @@ -1082,14 +1090,14 @@ public class AclDAOImpl implements AclDAO { return null; } - + acl.setProperties(properties); - + List> results = aclCrudDAO.getAcesAndAuthoritiesByAcl(id); - + List entries = new ArrayList(results.size()); for (Map result : results) - // for (AclMemberEntity member : members) + // for (AclMemberEntity member : members) { Boolean aceIsAllowed = (Boolean) result.get("allowed"); Integer aceType = (Integer) result.get("applies"); @@ -1097,7 +1105,7 @@ public class AclDAOImpl implements AclDAO Long permissionId = (Long) result.get("permissionId"); Integer position = (Integer) result.get("pos"); //Long result_aclmemId = (Long) result.get("aclmemId"); // not used here - + SimpleAccessControlEntry sacEntry = new SimpleAccessControlEntry(); sacEntry.setAccessStatus(aceIsAllowed ? AccessStatus.ALLOWED : AccessStatus.DENIED); sacEntry.setAceType(ACEType.getACETypeFromId(aceType)); @@ -1117,18 +1125,19 @@ public class AclDAOImpl implements AclDAO sacEntry.setPosition(position); entries.add(sacEntry); } - + Collections.sort(entries); acl.setEntries(entries); - + return acl; } - + /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#getInheritedAccessControlList(java.lang.Long) */ public Long getInheritedAccessControlList(Long id) { + aclCache.remove(id); AclUpdateEntity acl = aclCrudDAO.getAclForUpdate(id); if (acl.getAclType() == ACLType.OLD) { @@ -1138,9 +1147,9 @@ public class AclDAOImpl implements AclDAO { return acl.getInheritedAcl(); } - + Long inheritedAclId = null; - + if ((acl.getAclType() == ACLType.DEFINING) || (acl.getAclType() == ACLType.LAYERED)) { List changes = new ArrayList(); @@ -1159,22 +1168,22 @@ public class AclDAOImpl implements AclDAO acl.setInheritedAcl(acl.getId()); inheritedAclId = acl.getId(); } - + aclCrudDAO.updateAcl(acl); return inheritedAclId; } - + /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#mergeInheritedAccessControlList(java.lang.Long, java.lang.Long) */ public List mergeInheritedAccessControlList(Long inherited, Long target) { // TODO: For now we do a replace - we could do an insert if both inherit from the same acl - + List changes = new ArrayList(); - + Acl targetAcl = aclCrudDAO.getAcl(target); - + Acl inheritedAcl = null; if (inherited != null) { @@ -1197,9 +1206,9 @@ public class AclDAOImpl implements AclDAO if (!inheritedAcl.isLatest()) { final String searchAclId = inheritedAcl.getAclId(); - + Long actualInheritor = (Long)aclCrudDAO.getLatestAclByGuid(searchAclId); - + inheritedAcl = aclCrudDAO.getAcl(actualInheritor); if (inheritedAcl == null) { @@ -1215,10 +1224,10 @@ public class AclDAOImpl implements AclDAO return changes; } } - + // recursion test // if inherited already inherits from the target - + Acl test = inheritedAcl; while (test != null) { @@ -1236,29 +1245,29 @@ public class AclDAOImpl implements AclDAO test = aclCrudDAO.getAcl(test.getInheritsFrom()); } } - + if ((targetAcl.getAclType() != ACLType.DEFINING) && (targetAcl.getAclType() != ACLType.LAYERED)) { throw new IllegalArgumentException("Only defining ACLs can have their inheritance set"); } - + if (!targetAcl.getInherits()) { return changes; } - + Long actualInheritedId = inheritedAcl.getId(); - + if ((inheritedAcl.getAclType() == ACLType.DEFINING) || (inheritedAcl.getAclType() == ACLType.LAYERED)) { actualInheritedId = getInheritedAccessControlList(actualInheritedId); } // Will remove from the cache getWritable(target, actualInheritedId, null, null, actualInheritedId, true, changes, WriteMode.CHANGE_INHERITED); - + return changes; } - + /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#setAccessControlEntry(java.lang.Long, org.alfresco.repo.security.permissions.AccessControlEntry) */ @@ -1269,30 +1278,30 @@ public class AclDAOImpl implements AclDAO { throw new IllegalArgumentException("Shared ACLs are immutable"); } - + List changes = new ArrayList(); - + if ((ace.getPosition() != null) && (ace.getPosition() != 0)) { throw new IllegalArgumentException("Invalid position"); } - + // Find authority Authority authority = aclCrudDAO.getOrCreateAuthority(ace.getAuthority()); Permission permission = aclCrudDAO.getOrCreatePermission(ace.getPermission()); - + // Find context if (ace.getContext() != null) { throw new UnsupportedOperationException(); } - + // Find ACE Ace entry = aclCrudDAO.getOrCreateAce(permission, authority, ace.getAceType(), ace.getAccessStatus()); - + // Wire up // COW and remove any existing matches - + SimpleAccessControlEntry exclude = new SimpleAccessControlEntry(); // match any access status exclude.setAceType(ace.getAceType()); @@ -1303,19 +1312,19 @@ public class AclDAOImpl implements AclDAO toAdd.add(entry); // Will remove from the cache getWritable(id, null, Collections.singletonList(exclude), toAdd, null, true, changes, WriteMode.COPY_UPDATE_AND_INHERIT); - + return changes; } - + /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#enableInheritance(java.lang.Long, java.lang.Long) */ public List enableInheritance(Long id, Long parent) { List changes = new ArrayList(); - + AclUpdateEntity acl = aclCrudDAO.getAclForUpdate(id); - + switch (acl.getAclType()) { case FIXED: @@ -1331,7 +1340,7 @@ public class AclDAOImpl implements AclDAO case SHARED: // TODO support a list of children and casacade if given throw new IllegalArgumentException( - "Shared acls should be replace by creating a definig ACL, wiring it up for inhertitance, and then applying inheritance to any children. It can not be done by magic "); + "Shared acls should be replace by creating a definig ACL, wiring it up for inhertitance, and then applying inheritance to any children. It can not be done by magic "); case DEFINING: case LAYERED: default: @@ -1348,18 +1357,19 @@ public class AclDAOImpl implements AclDAO // Will remove from the cache getWritable(id, null, null, null, null, false, changes, WriteMode.COPY_ONLY); } - + List merged = mergeInheritedAccessControlList(parent, changes.get(0).getAfter()); changes.addAll(merged); return changes; } } - + /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#disableInheritance(java.lang.Long, boolean) */ public List disableInheritance(Long id, boolean setInheritedOnAcl) { + aclCache.remove(id); AclUpdateEntity acl = aclCrudDAO.getAclForUpdate(id); List changes = new ArrayList(1); switch (acl.getAclType()) @@ -1383,7 +1393,7 @@ public class AclDAOImpl implements AclDAO return disableInheritanceImpl(id, setInheritedOnAcl, acl); } } - + private Long getCopy(Long toCopy, Long toInheritFrom, ACLCopyMode mode) { AclUpdateEntity aclToCopy; @@ -1427,7 +1437,7 @@ public class AclDAOImpl implements AclDAO { aclToInheritFrom = aclCrudDAO.getAcl(toInheritFrom); } - + switch (aclToCopy.getAclType()) { case DEFINING: @@ -1471,7 +1481,7 @@ public class AclDAOImpl implements AclDAO { aclToInheritFrom = aclCrudDAO.getAcl(toInheritFrom); } - + switch (aclToCopy.getAclType()) { case DEFINING: @@ -1479,9 +1489,9 @@ public class AclDAOImpl implements AclDAO properties.setAclType(ACLType.DEFINING); properties.setInherits(aclToCopy.getInherits()); properties.setVersioned(true); - + Long id = createAccessControlList(properties).getId(); - + AccessControlList indirectAcl = getAccessControlList(toCopy); for (AccessControlEntry entry : indirectAcl.getEntries()) { @@ -1516,7 +1526,7 @@ public class AclDAOImpl implements AclDAO throw new UnsupportedOperationException(); } } - + /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#getDbAccessControlListCopy(java.lang.Long, java.lang.Long, org.alfresco.repo.security.permissions.ACLCopyMode) */ @@ -1524,7 +1534,7 @@ public class AclDAOImpl implements AclDAO { return getAclEntityCopy(toCopy, toInheritFrom, mode); } - + private Acl getAclEntityCopy(Long toCopy, Long toInheritFrom, ACLCopyMode mode) { Long id = getCopy(toCopy, toInheritFrom, mode); @@ -1534,99 +1544,99 @@ public class AclDAOImpl implements AclDAO } return aclCrudDAO.getAcl(id); } - + public List getAVMNodesByAcl(long aclEntityId, int maxResults) { return aclCrudDAO.getAVMNodesByAcl(aclEntityId, maxResults); } - + public List getADMNodesByAcl(long aclEntityId, int maxResults) { return aclCrudDAO.getADMNodesByAcl(aclEntityId, maxResults); } - + public Acl createLayeredAcl(Long indirectedAcl) { SimpleAccessControlListProperties properties = new SimpleAccessControlListProperties(); properties.setAclType(ACLType.LAYERED); - + Acl acl = createAccessControlList(properties); long id = acl.getId(); - + if (indirectedAcl != null) { mergeInheritedAccessControlList(indirectedAcl, id); } return acl; } - + private List disableInheritanceImpl(Long id, boolean setInheritedOnAcl, AclEntity aclIn) { List changes = new ArrayList(); - + if (!aclIn.getInherits()) { return Collections. emptyList(); } - + // Manages caching getWritable(id, null, null, null, null, false, changes, WriteMode.COPY_ONLY); AclUpdateEntity acl = aclCrudDAO.getAclForUpdate(changes.get(0).getAfter()); final Long inheritsFrom = acl.getInheritsFrom(); acl.setInherits(Boolean.FALSE); aclCrudDAO.updateAcl(acl); - + // Keep inherits from so we can reinstate if required // acl.setInheritsFrom(-1l); - + // Manages caching getWritable(acl.getId(), null, null, null, null, true, changes, WriteMode.TRUNCATE_INHERITED); - + // set Inherited - TODO: UNTESTED - + if ((inheritsFrom != null) && (inheritsFrom != -1) && setInheritedOnAcl) { // get aces for acl (via acl member) List members = aclCrudDAO.getAclMembersByAcl(inheritsFrom); - + for (AclMember member : members) { // TODO optimise Ace ace = aclCrudDAO.getAce(member.getAceId()); Authority authority = aclCrudDAO.getAuthority(ace.getAuthorityId()); - + SimpleAccessControlEntry entry = new SimpleAccessControlEntry(); entry.setAccessStatus(ace.isAllowed() ? AccessStatus.ALLOWED : AccessStatus.DENIED); entry.setAceType(ace.getAceType()); entry.setAuthority(authority.getAuthority()); - + /* NOTE: currently unused - intended for possible future enhancement if (ace.getContextId() != null) { AceContext aceContext = aclCrudDAO.getAceContext(ace.getContextId()); - + SimpleAccessControlEntryContext context = new SimpleAccessControlEntryContext(); context.setClassContext(aceContext.getClassContext()); context.setKVPContext(aceContext.getKvpContext()); context.setPropertyContext(aceContext.getPropertyContext()); entry.setContext(context); } - */ - + */ + Permission perm = aclCrudDAO.getPermission(ace.getPermissionId()); QName permTypeQName = qnameDAO.getQName(perm.getTypeQNameId()).getSecond(); // Has an ID so must exist SimplePermissionReference permissionRefernce = SimplePermissionReference.getPermissionReference(permTypeQName, perm.getName()); entry.setPermission(permissionRefernce); entry.setPosition(Integer.valueOf(0)); - + setAccessControlEntry(id, entry); } } return changes; } - + private static final String RESOURCE_KEY_ACL_CHANGE_SET_ID = "acl.change.set.id"; - + /** * Support to get the current ACL change set and bind this to the transaction. So we only make one new version of an * ACL per change set. If something is in the current change set we can update it. @@ -1637,7 +1647,7 @@ public class AclDAOImpl implements AclDAO if (changeSetId == null) { changeSetId = aclCrudDAO.createAclChangeSet(); - + // bind the id AlfrescoTransactionSupport.bindResource(RESOURCE_KEY_ACL_CHANGE_SET_ID, changeSetId); if (logger.isDebugEnabled()) @@ -1653,32 +1663,32 @@ public class AclDAOImpl implements AclDAO { throw new AlfrescoRuntimeException("Unexpected: missing change set "+changeSetId); } - + if (logger.isDebugEnabled()) { logger.debug("Existing change set = " + changeSetId); } - */ + */ } return changeSetId; } - + private static class AcePatternMatcher { private List patterns; - + AcePatternMatcher(List patterns) { this.patterns = patterns; } - + boolean matches(AclCrudDAO aclCrudDAO, Map result, int position) { if (patterns == null) { return true; } - + for (AccessControlEntry pattern : patterns) { if (checkPattern(aclCrudDAO, result, position, pattern)) @@ -1688,7 +1698,7 @@ public class AclDAOImpl implements AclDAO } return false; } - + private boolean checkPattern(AclCrudDAO aclCrudDAO, Map result, int position, AccessControlEntry pattern) { Boolean result_aceIsAllowed = (Boolean) result.get("allowed"); @@ -1697,7 +1707,7 @@ public class AclDAOImpl implements AclDAO Long result_permissionId = (Long) result.get("permissionId"); Integer result_position = (Integer) result.get("pos"); //Long result_aclmemId = (Long) result.get("aclmemId"); // not used - + if (pattern.getAccessStatus() != null) { if (pattern.getAccessStatus() != (result_aceIsAllowed ? AccessStatus.ALLOWED : AccessStatus.DENIED)) @@ -1705,7 +1715,7 @@ public class AclDAOImpl implements AclDAO return false; } } - + if (pattern.getAceType() != null) { if (pattern.getAceType() != ACEType.getACETypeFromId(result_aceType)) @@ -1713,7 +1723,7 @@ public class AclDAOImpl implements AclDAO return false; } } - + if (pattern.getAuthority() != null) { if ((pattern.getAuthorityType() != AuthorityType.WILDCARD) && !pattern.getAuthority().equals(result_authority)) @@ -1721,12 +1731,12 @@ public class AclDAOImpl implements AclDAO return false; } } - + if (pattern.getContext() != null) { throw new IllegalArgumentException("Context not yet supported"); } - + if (pattern.getPermission() != null) { Long permId = aclCrudDAO.getPermission(pattern.getPermission()).getId(); @@ -1735,7 +1745,7 @@ public class AclDAOImpl implements AclDAO return false; } } - + if (pattern.getPosition() != null) { if (pattern.getPosition().intValue() >= 0) @@ -1753,18 +1763,18 @@ public class AclDAOImpl implements AclDAO } } } - + return true; } } - + static class AclChangeImpl implements AclChange { private Long before; private Long after; private ACLType typeBefore; private ACLType typeAfter; - + public AclChangeImpl(Long before, Long after, ACLType typeBefore, ACLType typeAfter) { this.before = before; @@ -1772,17 +1782,17 @@ public class AclDAOImpl implements AclDAO this.typeAfter = typeAfter; this.typeBefore = typeBefore; } - + public Long getAfter() { return after; } - + public Long getBefore() { return before; } - + /** * @param after */ @@ -1798,12 +1808,12 @@ public class AclDAOImpl implements AclDAO { this.before = before; } - + public ACLType getTypeAfter() { return typeAfter; } - + /** * @param typeAfter */ @@ -1811,12 +1821,12 @@ public class AclDAOImpl implements AclDAO { this.typeAfter = typeAfter; } - + public ACLType getTypeBefore() { return typeBefore; } - + /** * @param typeBefore */ @@ -1824,7 +1834,7 @@ public class AclDAOImpl implements AclDAO { this.typeBefore = typeBefore; } - + @Override public String toString() { @@ -1835,7 +1845,7 @@ public class AclDAOImpl implements AclDAO return builder.toString(); } } - + /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#renameAuthority(java.lang.String, java.lang.String) */ @@ -1844,4 +1854,33 @@ public class AclDAOImpl implements AclDAO aclCrudDAO.renameAuthority(before, after); aclCache.clear(); } + + /* (non-Javadoc) + * @see org.alfresco.repo.domain.permissions.AclDAO#fixSharedAcl(java.lang.Long, java.lang.Long) + */ + @Override + public void fixSharedAcl(Long shared, Long defining) + { + Acl definingAcl = null; + if (defining != null) + { + definingAcl = aclCrudDAO.getAcl(defining); + } + else + { + throw new IllegalStateException("Null defining acl"); + } + + Acl sharedAcl = null; + if (shared != null) + { + sharedAcl = aclCrudDAO.getAcl(defining); + } + else + { + throw new IllegalStateException("Null shared acl"); + } + List changes = new ArrayList(); + getWritable(shared, defining, null, null, defining, true, changes, WriteMode.CHANGE_INHERITED); + } } diff --git a/source/java/org/alfresco/repo/forms/processor/workflow/AbstractWorkflowFormProcessor.java b/source/java/org/alfresco/repo/forms/processor/workflow/AbstractWorkflowFormProcessor.java index 491cc2dcb1..941fc612d6 100644 --- a/source/java/org/alfresco/repo/forms/processor/workflow/AbstractWorkflowFormProcessor.java +++ b/source/java/org/alfresco/repo/forms/processor/workflow/AbstractWorkflowFormProcessor.java @@ -31,6 +31,7 @@ import org.alfresco.repo.forms.FormData.FieldData; import org.alfresco.repo.forms.processor.FormCreationData; import org.alfresco.repo.forms.processor.node.ContentModelFormProcessor; import org.alfresco.repo.forms.processor.node.ContentModelItemData; +import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.workflow.WorkflowModel; import org.alfresco.service.cmr.workflow.WorkflowService; import org.alfresco.util.ParameterCheck; @@ -49,6 +50,8 @@ public abstract class AbstractWorkflowFormProcessor exten /** WorkflowService */ protected WorkflowService workflowService; + protected BehaviourFilter behaviourFilter; + @Override protected void populateForm(Form form, List fields, FormCreationData data) { @@ -82,6 +85,14 @@ public abstract class AbstractWorkflowFormProcessor exten this.workflowService = workflowService; } + /** + * @param behaviourFilter the behaviourFilter to set + */ + public void setBehaviourFilter(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + /* * @see org.alfresco.repo.forms.processor.node.NodeFormProcessor#getTypedItem(org.alfresco.repo.forms.Item) */ diff --git a/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormPersister.java b/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormPersister.java index 631ba58e21..f831d76107 100644 --- a/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormPersister.java +++ b/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormPersister.java @@ -24,6 +24,7 @@ import java.util.List; import org.alfresco.repo.forms.FormData.FieldData; import org.alfresco.repo.forms.processor.node.ContentModelItemData; +import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.workflow.TaskUpdater; import org.alfresco.service.cmr.dictionary.DictionaryService; @@ -53,7 +54,7 @@ public class TaskFormPersister extends ContentModelFormPersister WorkflowService workflowService, NodeService nodeService, AuthenticationService authenticationService, - Log logger) + BehaviourFilter behaviourFilter, Log logger) { super(itemData, namespaceService, dictionaryService, logger); WorkflowTask item = itemData.getItem(); @@ -64,7 +65,7 @@ public class TaskFormPersister extends ContentModelFormPersister throw new AccessDeniedException("Failed to update task with id '" + item.getId() + "'."); } - this.updater = new TaskUpdater(item.id, workflowService, nodeService); + this.updater = new TaskUpdater(item.id, workflowService, nodeService, behaviourFilter); } /** diff --git a/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormProcessor.java b/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormProcessor.java index 52c3a00a7c..0040b75fd1 100644 --- a/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormProcessor.java +++ b/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormProcessor.java @@ -300,6 +300,6 @@ public class TaskFormProcessor extends AbstractWorkflowFormProcessor itemData = makeItemData(item); return new TaskFormPersister(itemData, namespaceService, dictionaryService, - workflowService, nodeService, authenticationService, LOGGER); + workflowService, nodeService, authenticationService, behaviourFilter, LOGGER); } } diff --git a/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormProcessorTest.java b/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormProcessorTest.java index 4fdeba8070..aff66b25a4 100644 --- a/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormProcessorTest.java +++ b/source/java/org/alfresco/repo/forms/processor/workflow/TaskFormProcessorTest.java @@ -45,6 +45,7 @@ import org.alfresco.repo.forms.FormData.FieldData; import org.alfresco.repo.forms.processor.node.DefaultFieldProcessor; import org.alfresco.repo.forms.processor.node.MockClassAttributeDefinition; import org.alfresco.repo.forms.processor.node.MockFieldProcessorRegistry; +import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; @@ -576,6 +577,7 @@ public class TaskFormProcessorTest extends TestCase processor1.setAuthenticationService(authenticationService); processor1.setPersonService(personService); processor1.setFieldProcessorRegistry(fieldProcessorRegistry); + processor1.setBehaviourFilter(mock(BehaviourFilter.class)); return processor1; } diff --git a/source/java/org/alfresco/repo/forms/processor/workflow/WorkflowFormPersister.java b/source/java/org/alfresco/repo/forms/processor/workflow/WorkflowFormPersister.java index 9a6787f04e..a1ff85427e 100644 --- a/source/java/org/alfresco/repo/forms/processor/workflow/WorkflowFormPersister.java +++ b/source/java/org/alfresco/repo/forms/processor/workflow/WorkflowFormPersister.java @@ -23,6 +23,7 @@ import java.io.Serializable; import java.util.List; import org.alfresco.repo.forms.processor.node.ContentModelItemData; +import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.workflow.WorkflowBuilder; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.repository.NodeRef; @@ -49,11 +50,11 @@ public class WorkflowFormPersister extends ContentModelFormPersister makeFormPersister(WorkflowDefinition item) { ContentModelItemData itemData = makeItemData(item); - return new WorkflowFormPersister(itemData, namespaceService, dictionaryService, workflowService, nodeService, logger); + return new WorkflowFormPersister(itemData, namespaceService, dictionaryService, workflowService, nodeService, behaviourFilter, logger); } /* diff --git a/source/java/org/alfresco/repo/forms/processor/workflow/WorkflowFormProcessorTest.java b/source/java/org/alfresco/repo/forms/processor/workflow/WorkflowFormProcessorTest.java index 6720a7591e..a69bb7bc41 100644 --- a/source/java/org/alfresco/repo/forms/processor/workflow/WorkflowFormProcessorTest.java +++ b/source/java/org/alfresco/repo/forms/processor/workflow/WorkflowFormProcessorTest.java @@ -61,6 +61,7 @@ import org.alfresco.repo.forms.FormData.FieldData; import org.alfresco.repo.forms.processor.node.DefaultFieldProcessor; import org.alfresco.repo.forms.processor.node.MockClassAttributeDefinition; import org.alfresco.repo.forms.processor.node.MockFieldProcessorRegistry; +import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.workflow.WorkflowModel; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; @@ -445,10 +446,10 @@ public class WorkflowFormProcessorTest extends TestCase MockFieldProcessorRegistry fieldProcessorRegistry = new MockFieldProcessorRegistry(namespaceService, dictionaryService); DefaultFieldProcessor defaultProcessor = makeDefaultFieldProcessor(dictionaryService); - processor = makeTaskFormProcessor(dictionaryService, fieldProcessorRegistry, defaultProcessor); + processor = makeWorkflowFormProcessor(dictionaryService, fieldProcessorRegistry, defaultProcessor); } - private WorkflowFormProcessor makeTaskFormProcessor(DictionaryService dictionaryService, + private WorkflowFormProcessor makeWorkflowFormProcessor(DictionaryService dictionaryService, MockFieldProcessorRegistry fieldProcessorRegistry, DefaultFieldProcessor defaultProcessor) { WorkflowFormProcessor processor1 = new WorkflowFormProcessor(); @@ -457,6 +458,7 @@ public class WorkflowFormProcessorTest extends TestCase processor1.setNamespaceService(namespaceService); processor1.setDictionaryService(dictionaryService); processor1.setFieldProcessorRegistry(fieldProcessorRegistry); + processor1.setBehaviourFilter(mock(BehaviourFilter.class)); return processor1; } diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java b/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java index f12c4796cf..901f7b9ff0 100644 --- a/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java +++ b/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java @@ -130,11 +130,6 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab */ private boolean selectable; - /** - * Defines whether the folder is read-only for user or not. - */ - private Boolean readOnly; - /** * The UIDValidity */ @@ -282,18 +277,6 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab { setSelectable(selectable); } - - AccessStatus status = serviceRegistry.getPublicServiceAccessService().hasAccess(ServiceRegistry.NODE_SERVICE.getLocalName(), "createNode", folderInfo.getNodeRef(), null, null, null); - //serviceRegistry.getPermissionService().hasPermission(folderInfo.getNodeRef(), PermissionService.WRITE); - if (status == AccessStatus.DENIED) - { - readOnly = true; - } - else - { - readOnly = false; - } - } else { @@ -368,7 +351,7 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab @Override public void deleteAllMessagesInternal() throws FolderException { - if (this.readOnly) + if (isReadOnly()) { throw new FolderException("Can't delete all - Permission denied"); } @@ -390,7 +373,7 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab @Override protected void expungeInternal() throws FolderException { - if (this.readOnly) + if (isReadOnly()) { throw new FolderException("Can't expunge - Permission denied"); } @@ -1047,7 +1030,9 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab @Override protected boolean isReadOnly() { - return readOnly; + AccessStatus status = serviceRegistry.getPublicServiceAccessService().hasAccess(ServiceRegistry.NODE_SERVICE.getLocalName(), "createNode", folderInfo.getNodeRef(), null, null, null); + //serviceRegistry.getPermissionService().hasPermission(folderInfo.getNodeRef(), PermissionService.WRITE); + return status == AccessStatus.DENIED; } public ImapViewMode getViewMode() diff --git a/source/java/org/alfresco/repo/imap/ImapService.java b/source/java/org/alfresco/repo/imap/ImapService.java index 38ff947270..da0b8b72eb 100644 --- a/source/java/org/alfresco/repo/imap/ImapService.java +++ b/source/java/org/alfresco/repo/imap/ImapService.java @@ -74,7 +74,7 @@ public interface ImapService * Returns an collection of mailboxes. This method serves LIST command of the IMAP protocol. * * @param user User making the request - * @param mailboxPattern String name of a mailbox possible including a wildcard. + * @param mailboxPattern String name of a mailbox encoded in MUTF-7, possible including a wildcard. * @return Collection of mailboxes matching the pattern. */ public List listMailboxes(AlfrescoImapUser user, String mailboxPattern); @@ -83,7 +83,7 @@ public interface ImapService * Returns an collection of subscribed mailboxes. This method serves LSUB command of the IMAP protocol. * * @param user User making the request - * @param mailboxPattern String name of a mailbox possible including a wildcard. + * @param mailboxPattern String name of a mailbox encoded in MUTF-7, possible including a wildcard. * @return Collection of mailboxes matching the pattern. */ public List listSubscribedMailboxes(AlfrescoImapUser user, String mailboxPattern); @@ -93,7 +93,7 @@ public interface ImapService * user has rights to create. This method serves CREATE command of the IMAP protocol. * * @param user User making the request. - * @param mailboxName String name of the target + * @param mailboxName String name of the target encoded in MUTF-7, * @return an Mailbox reference. */ public AlfrescoImapFolder createMailbox(AlfrescoImapUser user, String mailboxName); @@ -103,7 +103,7 @@ public interface ImapService * protocol. * * @param user User making the request. - * @param mailboxName String name of the target + * @param mailboxName String name of the target encoded in MUTF-7, * @throws com.icegreen.greenmail.store.FolderException if mailbox has a non-selectable store with children */ public void deleteMailbox(AlfrescoImapUser user, String mailboxName); @@ -115,8 +115,8 @@ public interface ImapService * protocol. * * @param user User making the request. - * @param oldMailboxName String name of the existing folder - * @param newMailboxName String target new name + * @param oldMailboxName String name of the existing folder encoded in MUTF-7, + * @param newMailboxName String target new name encoded in MUTF-7, */ public void renameMailbox(AlfrescoImapUser user, String oldMailboxName, String newMailboxName); @@ -125,7 +125,7 @@ public interface ImapService * also can be used by to obtain hierarchy delimiter by the LIST command:

C: 2 list "" ""

S: * LIST () "." ""

S: 2 OK LIST completed. * * @param user User making the request. - * @param mailboxName String name of the target. + * @param mailboxName String name of the target encoded in MUTF-7,. * @return an Mailbox reference. */ public AlfrescoImapFolder getFolder(AlfrescoImapUser user, String mailboxName); @@ -143,7 +143,7 @@ public interface ImapService * Subscribes a user to a mailbox. The mailbox must exist locally and the user must have rights to modify it.

This method serves SUBSCRIBE command of the IMAP protocol. * * @param user User making the request - * @param mailbox String representation of a mailbox name. + * @param mailbox String representation of a mailbox name encoded in MUTF-7,. */ public void subscribe(AlfrescoImapUser user, String mailbox); @@ -151,7 +151,7 @@ public interface ImapService * Unsubscribes from a given mailbox.

This method serves UNSUBSCRIBE command of the IMAP protocol. * * @param user User making the request - * @param mailbox String representation of a mailbox name. + * @param mailbox String representation of a mailbox name encoded in MUTF-7,. */ public void unsubscribe(AlfrescoImapUser user, String mailbox); diff --git a/source/java/org/alfresco/repo/imap/ImapServiceImpl.java b/source/java/org/alfresco/repo/imap/ImapServiceImpl.java index 95f13412a0..0084fd942b 100644 --- a/source/java/org/alfresco/repo/imap/ImapServiceImpl.java +++ b/source/java/org/alfresco/repo/imap/ImapServiceImpl.java @@ -542,6 +542,9 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol { throw new IllegalArgumentException(ERROR_MAILBOX_NAME_IS_MANDATORY); } + + AlfrescoImapFolder sourceNode = getFolder(user, oldMailboxName); + oldMailboxName = Utf7.decode(oldMailboxName, Utf7.UTF7_MODIFIED); newMailboxName = Utf7.decode(newMailboxName, Utf7.UTF7_MODIFIED); if (logger.isDebugEnabled()) @@ -549,8 +552,6 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol logger.debug("Renaming folder oldMailboxName=" + oldMailboxName + " newMailboxName=" + newMailboxName); } - AlfrescoImapFolder sourceNode = getFolder(user, oldMailboxName); - NodeRef root = getMailboxRootRef(oldMailboxName, user.getLogin()); String[] folderNames = getMailPathInRepo(newMailboxName).split(String.valueOf(AlfrescoImapConst.HIERARCHY_DELIMITER)); String folderName = null; @@ -633,11 +634,6 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol } } - /** - * Get Folder - * @param user - * @param mailboxName - */ public AlfrescoImapFolder getFolder(AlfrescoImapUser user, String mailboxName) { if (logger.isDebugEnabled()) diff --git a/source/java/org/alfresco/repo/imap/ImapServiceImplTest.java b/source/java/org/alfresco/repo/imap/ImapServiceImplTest.java index 553248068c..5a84bb0d20 100644 --- a/source/java/org/alfresco/repo/imap/ImapServiceImplTest.java +++ b/source/java/org/alfresco/repo/imap/ImapServiceImplTest.java @@ -55,6 +55,7 @@ import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.PropertyMap; +import org.alfresco.util.Utf7; import org.alfresco.util.config.RepositoryFolderConfigBean; import org.springframework.context.ApplicationContext; import org.springframework.core.io.ClassPathResource; @@ -510,4 +511,19 @@ public class ImapServiceImplTest extends TestCase assertTrue(fl.contains(flags)); } } + + public void testRenameAccentedMailbox() throws Exception + { + String MAILBOX_ACCENTED_NAME_A = "Htel"; + String MAILBOX_ACCENTED_NAME_B = "HtelXX"; + + imapService.createMailbox(user, MAILBOX_ACCENTED_NAME_A); + imapService.deleteMailbox(user, MAILBOX_ACCENTED_NAME_A); + + imapService.createMailbox(user, MAILBOX_ACCENTED_NAME_A); + imapService.renameMailbox(user, MAILBOX_ACCENTED_NAME_A, MAILBOX_ACCENTED_NAME_B); + assertFalse("Can't rename mailbox", checkMailbox(user, MAILBOX_ACCENTED_NAME_A)); + assertTrue("Can't rename mailbox", checkMailbox(user, MAILBOX_ACCENTED_NAME_B)); + imapService.deleteMailbox(user, MAILBOX_ACCENTED_NAME_B); + } } diff --git a/source/java/org/alfresco/repo/importer/ACPImportPackageHandler.java b/source/java/org/alfresco/repo/importer/ACPImportPackageHandler.java index 83e4143f38..a06e7536b3 100644 --- a/source/java/org/alfresco/repo/importer/ACPImportPackageHandler.java +++ b/source/java/org/alfresco/repo/importer/ACPImportPackageHandler.java @@ -29,8 +29,8 @@ import java.util.Enumeration; import org.alfresco.service.cmr.view.ImportPackageHandler; import org.alfresco.service.cmr.view.ImporterException; -import org.apache.tools.zip.ZipEntry; -import org.apache.tools.zip.ZipFile; +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipFile; /** @@ -86,7 +86,7 @@ public class ACPImportPackageHandler try { // find xml meta-data file - ZipEntry xmlMetaDataEntry = null; + ZipArchiveEntry xmlMetaDataEntry = null; // TODO: First, locate xml meta-data file by name @@ -94,7 +94,7 @@ public class ACPImportPackageHandler Enumeration entries = zipFile.getEntries(); while(entries.hasMoreElements()) { - ZipEntry entry = (ZipEntry)entries.nextElement(); + ZipArchiveEntry entry = (ZipArchiveEntry)entries.nextElement(); if (!entry.isDirectory()) { // Locate xml file in root of .acp @@ -136,7 +136,7 @@ public class ACPImportPackageHandler */ public InputStream importStream(String content) { - ZipEntry zipEntry = zipFile.getEntry(content); + ZipArchiveEntry zipEntry = zipFile.getEntry(content); if (zipEntry == null) { // Note: for some reason, when modifying a zip archive the path seperator changes diff --git a/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java b/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java index 65e7788908..337883e1f4 100644 --- a/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java +++ b/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java @@ -20,7 +20,6 @@ package org.alfresco.repo.invitation; import java.io.Serializable; -import java.text.MessageFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -38,6 +37,7 @@ import org.alfresco.repo.security.authentication.UserNameGenerator; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.site.SiteModel; import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.invitation.Invitation; import org.alfresco.service.cmr.invitation.InvitationException; import org.alfresco.service.cmr.invitation.InvitationExceptionForbidden; @@ -66,9 +66,9 @@ import org.alfresco.service.cmr.workflow.WorkflowTaskState; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.GUID; +import org.alfresco.util.PropertyCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.alfresco.util.PropertyCheck; import org.springframework.extensions.surf.util.I18NUtil; /** @@ -89,6 +89,7 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli private SiteService siteService; private MutableAuthenticationService authenticationService; private PermissionService permissionService; + private DictionaryService dictionaryService; private NamespaceService namespaceService; private NodeService nodeService; // user name and password generation beans @@ -890,6 +891,11 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli return passwordGenerator; } + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + public void setNamespaceService(NamespaceService namespaceService) { this.namespaceService = namespaceService; @@ -1415,7 +1421,7 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli public Object doWork() throws Exception { QName type = nodeService.getType(siteRef); - if (type.equals(SiteModel.TYPE_SITE)) + if (dictionaryService.isSubClass(type, SiteModel.TYPE_SITE)) { // this is a web site being deleted. String siteName = (String) nodeService.getProperty(siteRef, ContentModel.PROP_NAME); diff --git a/source/java/org/alfresco/repo/jgroups/AlfrescoJGroupsChannelFactory.java b/source/java/org/alfresco/repo/jgroups/AlfrescoJGroupsChannelFactory.java index f099815085..ebd3a85ebc 100644 --- a/source/java/org/alfresco/repo/jgroups/AlfrescoJGroupsChannelFactory.java +++ b/source/java/org/alfresco/repo/jgroups/AlfrescoJGroupsChannelFactory.java @@ -515,6 +515,12 @@ public class AlfrescoJGroupsChannelFactory extends AbstractLifecycleBean public static class DummyProtocol extends LOOPBACK { + public DummyProtocol() + { + super(); + enable_diagnostics = false; + } + @Override public String getName() { diff --git a/source/java/org/alfresco/repo/jscript/ScriptNode.java b/source/java/org/alfresco/repo/jscript/ScriptNode.java index 9db14f777d..83da9fb564 100644 --- a/source/java/org/alfresco/repo/jscript/ScriptNode.java +++ b/source/java/org/alfresco/repo/jscript/ScriptNode.java @@ -1528,11 +1528,26 @@ public class ScriptNode implements Serializable, Scopeable, NamespacePrefixResol * @return Newly created Node or null if failed to create. */ public ScriptNode createFile(String name) + { + return createFile(name, null); + } + + /** + * Create a new File (cm:content) node as a child of this node. + *

+ * Once created the file should have content set using the content property. + * + * @param name Name of the file to create + * @param type Type of the file to create (if null, defaults to ContentModel.TYPE_CONTENT) + * + * @return Newly created Node or null if failed to create. + */ + public ScriptNode createFile(String name, String type) { ParameterCheck.mandatoryString("Node Name", name); FileInfo fileInfo = this.services.getFileFolderService().create( - this.nodeRef, name, ContentModel.TYPE_CONTENT); + this.nodeRef, name, type == null ? ContentModel.TYPE_CONTENT : createQName(type)); reset(); @@ -1550,17 +1565,30 @@ public class ScriptNode implements Serializable, Scopeable, NamespacePrefixResol * @return Newly created Node or null if failed to create. */ public ScriptNode createFolder(String name) + { + return createFolder(name, null); + } + + /** + * Create a new folder (cm:folder) node as a child of this node. + * + * @param name Name of the folder to create + * @param type Type of the folder to create (if null, defaults to ContentModel.TYPE_FOLDER) + * + * @return Newly created Node or null if failed to create. + */ + public ScriptNode createFolder(String name, String type) { ParameterCheck.mandatoryString("Node Name", name); FileInfo fileInfo = this.services.getFileFolderService().create( - this.nodeRef, name, ContentModel.TYPE_FOLDER); + this.nodeRef, name, type == null ? ContentModel.TYPE_FOLDER : createQName(type)); reset(); return newInstance(fileInfo.getNodeRef(), this.services, this.scope); } - + /** * Create a new Node of the specified type as a child of this node. * diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java index dc01f65e45..4e68a4fa9f 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java @@ -34,9 +34,7 @@ import java.util.Stack; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; -import org.alfresco.repo.rule.ruletrigger.RuleTrigger; import org.alfresco.repo.search.QueryParameterDefImpl; -import org.alfresco.repo.transaction.TransactionalResourceHelper; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.model.FileExistsException; @@ -714,16 +712,6 @@ public class FileFolderServiceImpl implements FileFolderService */ public FileInfo rename(NodeRef sourceNodeRef, String newName) throws FileExistsException, FileNotFoundException { - // NOTE: - // - // This information is placed in the transaction to indicate that a rename has taken place. This information is - // used by the rule trigger to ensure inbound rule is not triggered by a file rename - // - // See http://issues.alfresco.com/browse/AR-1544 - Set nodeRefRenameSet = TransactionalResourceHelper.getSet(RuleTrigger.RULE_TRIGGER_NODESET); - String marker = sourceNodeRef.toString()+"rename"; - nodeRefRenameSet.add(marker); - return moveOrCopy(sourceNodeRef, null, null, newName, true); } diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java index 97dc5697c5..9b9d3a3c45 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java @@ -22,9 +22,11 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.Locale; +import javax.transaction.Status; import javax.transaction.UserTransaction; import junit.framework.TestCase; @@ -36,6 +38,7 @@ import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.dictionary.DictionaryDAO; import org.alfresco.repo.dictionary.M2Model; import org.alfresco.repo.dictionary.M2Type; +import org.alfresco.repo.domain.node.AbstractNodeDAOImpl; import org.alfresco.repo.node.integrity.IntegrityChecker; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; @@ -152,7 +155,10 @@ public class FileFolderServiceImplTest extends TestCase { try { - txn.rollback(); + if (txn.getStatus() != Status.STATUS_ROLLEDBACK && txn.getStatus() != Status.STATUS_COMMITTED) + { + txn.rollback(); + } } catch (Throwable e) { @@ -895,7 +901,7 @@ public class FileFolderServiceImplTest extends TestCase ContentReader reader = fileFolderService.getReader(fileNodeRef); assertEquals("Mimetype was not automatically set", MimetypeMap.MIMETYPE_HTML, reader.getMimetype()); } - + @SuppressWarnings("unused") public void testGetLocalizedSibling() throws Exception { @@ -938,4 +944,147 @@ public class FileFolderServiceImplTest extends TestCase I18NUtil.setLocale(Locale.US); assertEquals("Match fail for " + I18NUtil.getLocale(), mnode, fileFolderService.getLocalizedSibling(mnode)); } + + /** + * Ensures that timestamp propagation can be successfully enabled.
+ * ALF-7421 + */ + public synchronized void testAlf7421TimestampPropagation() throws Exception + { + // Terminate the transaction + txn.commit(); + + nodeService.addAspect(workingRootNodeRef, ContentModel.ASPECT_AUDITABLE, null); + + // Get the current dates for the parent folder (one level up) + String creatorTooHigh = (String) nodeService.getProperty(workingRootNodeRef, ContentModel.PROP_CREATOR); + Date createdTooHigh = (Date) nodeService.getProperty(workingRootNodeRef, ContentModel.PROP_CREATED); + String modifierTooHigh = (String) nodeService.getProperty(workingRootNodeRef, ContentModel.PROP_MODIFIER); + Date modifiedTooHigh = (Date) nodeService.getProperty(workingRootNodeRef, ContentModel.PROP_MODIFIED); + + FileInfo folderInfo = fileFolderService.create(workingRootNodeRef, "SomeFolder", ContentModel.TYPE_FOLDER); + NodeRef folderNodeRef = folderInfo.getNodeRef(); + // Get the dates for the folder we are using + String creatorExpected = (String) nodeService.getProperty(folderNodeRef, ContentModel.PROP_CREATOR); + Date createdExpected = (Date) nodeService.getProperty(folderNodeRef, ContentModel.PROP_CREATED); + String modifierExpected = (String) nodeService.getProperty(folderNodeRef, ContentModel.PROP_MODIFIER); + Date modifiedExpected = (Date) nodeService.getProperty(folderNodeRef, ContentModel.PROP_MODIFIED); + + // Create a new file and check the parent (expect no changes) + FileInfo fileInfo = fileFolderService.create(folderNodeRef, "Something.html", ContentModel.TYPE_CONTENT); + NodeRef fileNodeRef = fileInfo.getNodeRef(); + nodeService.addAspect(fileNodeRef, ContentModel.ASPECT_AUDITABLE, null); + + assertEquals("cm:creator should not have changed", + creatorExpected, + nodeService.getProperty(folderNodeRef, ContentModel.PROP_CREATOR)); + assertEquals("cm:created should not have changed", + createdExpected, + nodeService.getProperty(folderNodeRef, ContentModel.PROP_CREATED)); + assertEquals("cm:modifier should not have changed", + modifierExpected, + nodeService.getProperty(folderNodeRef, ContentModel.PROP_MODIFIER)); + assertEquals("cm:modified should not have changed", + modifiedExpected, + nodeService.getProperty(folderNodeRef, ContentModel.PROP_MODIFIED)); + // Update the child and check parent (expect no changes) + fileFolderService.rename(fileNodeRef, "something.html"); + assertEquals("cm:creator should not have changed", + creatorExpected, + nodeService.getProperty(folderNodeRef, ContentModel.PROP_CREATOR)); + assertEquals("cm:created should not have changed", + createdExpected, + nodeService.getProperty(folderNodeRef, ContentModel.PROP_CREATED)); + assertEquals("cm:modifier should not have changed", + modifierExpected, + nodeService.getProperty(folderNodeRef, ContentModel.PROP_MODIFIER)); + assertEquals("cm:modified should not have changed", + modifiedExpected, + nodeService.getProperty(folderNodeRef, ContentModel.PROP_MODIFIED)); + // Delete node and check parent (expect no changes) + fileFolderService.delete(fileNodeRef); + assertEquals("cm:creator should not have changed", + creatorExpected, + nodeService.getProperty(folderNodeRef, ContentModel.PROP_CREATOR)); + assertEquals("cm:created should not have changed", + createdExpected, + nodeService.getProperty(folderNodeRef, ContentModel.PROP_CREATED)); + assertEquals("cm:modifier should not have changed", + modifierExpected, + nodeService.getProperty(folderNodeRef, ContentModel.PROP_MODIFIER)); + assertEquals("cm:modified should not have changed", + modifiedExpected, + nodeService.getProperty(folderNodeRef, ContentModel.PROP_MODIFIED)); + + // Force timestamp propagation + AbstractNodeDAOImpl nodeDAO = (AbstractNodeDAOImpl) ctx.getBean("nodeDAO"); + nodeDAO.setEnableTimestampPropagation(true); + try + { + // Create a new file and check the parent (expect modifier changes) + fileInfo = fileFolderService.create(folderNodeRef, "Something.html", ContentModel.TYPE_CONTENT); + fileNodeRef = fileInfo.getNodeRef(); + nodeService.addAspect(fileNodeRef, ContentModel.ASPECT_AUDITABLE, null); + + assertEquals("cm:creator should not have changed", + creatorExpected, + nodeService.getProperty(folderNodeRef, ContentModel.PROP_CREATOR)); + assertEquals("cm:created should not have changed", + createdExpected, + nodeService.getProperty(folderNodeRef, ContentModel.PROP_CREATED)); + assertEquals("cm:modifier should have changed", + nodeService.getProperty(fileNodeRef, ContentModel.PROP_MODIFIER), + nodeService.getProperty(folderNodeRef, ContentModel.PROP_MODIFIER)); + assertEquals("cm:modified should have changed", + nodeService.getProperty(fileNodeRef, ContentModel.PROP_MODIFIED), + nodeService.getProperty(folderNodeRef, ContentModel.PROP_MODIFIED)); + // Update the child and check parent (expect modifier changes) + fileFolderService.rename(fileNodeRef, "something.html"); + assertEquals("cm:creator should not have changed", + creatorExpected, + nodeService.getProperty(folderNodeRef, ContentModel.PROP_CREATOR)); + assertEquals("cm:created should not have changed", + createdExpected, + nodeService.getProperty(folderNodeRef, ContentModel.PROP_CREATED)); + assertEquals("cm:modifier should have changed", + nodeService.getProperty(fileNodeRef, ContentModel.PROP_MODIFIER), + nodeService.getProperty(folderNodeRef, ContentModel.PROP_MODIFIER)); + assertEquals("cm:modified should have changed", + nodeService.getProperty(fileNodeRef, ContentModel.PROP_MODIFIED), + nodeService.getProperty(folderNodeRef, ContentModel.PROP_MODIFIED)); + // Delete node and check parent (expect modifier changes) + modifiedExpected = (Date) nodeService.getProperty(workingRootNodeRef, ContentModel.PROP_MODIFIED); + fileFolderService.delete(fileNodeRef); + assertEquals("cm:creator should not have changed", + creatorExpected, + nodeService.getProperty(folderNodeRef, ContentModel.PROP_CREATOR)); + assertEquals("cm:created should not have changed", + createdExpected, + nodeService.getProperty(folderNodeRef, ContentModel.PROP_CREATED)); + assertSame("cm:modifier should have changed", + modifierExpected, + nodeService.getProperty(folderNodeRef, ContentModel.PROP_MODIFIER)); + assertNotSame("cm:modified should have changed", + modifiedExpected, + nodeService.getProperty(folderNodeRef, ContentModel.PROP_MODIFIED)); + } + finally + { + nodeDAO.setEnableTimestampPropagation(false); + } + + // Finally check that the second level up was NOT modified + assertEquals("cm:creator should not have changed (level too high)", + creatorTooHigh, + nodeService.getProperty(workingRootNodeRef, ContentModel.PROP_CREATOR)); + assertEquals("cm:created should not have changed (level too high)", + createdTooHigh, + nodeService.getProperty(workingRootNodeRef, ContentModel.PROP_CREATED)); + assertEquals("cm:modifier should not have changed (level too high)", + modifierTooHigh, + nodeService.getProperty(workingRootNodeRef, ContentModel.PROP_MODIFIER)); + assertEquals("cm:modified should not have changed (level too high)", + modifiedTooHigh, + nodeService.getProperty(workingRootNodeRef, ContentModel.PROP_MODIFIED)); + } } diff --git a/source/java/org/alfresco/repo/model/ml/tools/EditionServiceImplTest.java b/source/java/org/alfresco/repo/model/ml/tools/EditionServiceImplTest.java index d1b556a6a0..db8a111cdf 100644 --- a/source/java/org/alfresco/repo/model/ml/tools/EditionServiceImplTest.java +++ b/source/java/org/alfresco/repo/model/ml/tools/EditionServiceImplTest.java @@ -66,7 +66,7 @@ public class EditionServiceImplTest extends AbstractMultilingualTestCases Version rootEdition = editionService.getEditions(mlContainerNodeRef).getAllVersions().iterator().next(); // Ensure that the version label is 1.0 - assertTrue("The edition label would be 1.0 and not " + rootEdition.getVersionLabel(), rootEdition.getVersionLabel().equals("1.0")); + assertTrue("The edition label would be 0.1 and not " + rootEdition.getVersionLabel(), rootEdition.getVersionLabel().equals("0.1")); /* * default (1.1) @@ -76,7 +76,7 @@ public class EditionServiceImplTest extends AbstractMultilingualTestCases editions = new ArrayList(editionService.getEditions(mlContainerNodeRef).getAllVersions()); Version firstEdition = editions.get(0); // Ensure that the version label is 1.1 - assertTrue("The edition label would be 1.1 and not " + firstEdition.getVersionLabel(), firstEdition.getVersionLabel().equals("1.1")); + assertTrue("The edition label would be 0.2 and not " + firstEdition.getVersionLabel(), firstEdition.getVersionLabel().equals("0.2")); /* * major (2.0) @@ -87,8 +87,8 @@ public class EditionServiceImplTest extends AbstractMultilingualTestCases pivot = editionService.createEdition(pivot, versionProperties); editions = new ArrayList(editionService.getEditions(mlContainerNodeRef).getAllVersions()); Version secondEdition = editions.get(0); - // Ensure that the version label is 2.0 - assertTrue("The edition label would be 2.0 and not " + secondEdition.getVersionLabel(), secondEdition.getVersionLabel().equals("2.0")); + // Ensure that the version label is 1.0 + assertTrue("The edition label would be 1.0 and not " + secondEdition.getVersionLabel(), secondEdition.getVersionLabel().equals("1.0")); /* * minor (2.1) @@ -100,7 +100,7 @@ public class EditionServiceImplTest extends AbstractMultilingualTestCases editions = new ArrayList(editionService.getEditions(mlContainerNodeRef).getAllVersions()); Version thirdEdition = editions.get(0); // Ensure that the version label is 2.1 - assertTrue("The edition label would be 2.1 and not " + thirdEdition.getVersionLabel(), thirdEdition.getVersionLabel().equals("2.1")); + assertTrue("The edition label would be 1.1 and not " + thirdEdition.getVersionLabel(), thirdEdition.getVersionLabel().equals("1.1")); } public void testCreateEdition() throws Exception @@ -124,8 +124,8 @@ public class EditionServiceImplTest extends AbstractMultilingualTestCases assertTrue("The locale of the conatiner should be changed", nodeService.getProperty(mlContainerNodeRef, ContentModel.PROP_LOCALE).equals(Locale.FRENCH)); // get the two editions - Version rootEdition = editionHistory.getVersion("1.0"); - Version actualEdition = editionHistory.getVersion("1.1"); + Version rootEdition = editionHistory.getVersion("0.1"); + Version actualEdition = editionHistory.getVersion("0.2"); // get the translations of the root versions List rootVersionTranslations = editionService.getVersionedTranslations(rootEdition); diff --git a/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java b/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java index 684c29ace4..90c82bc9d2 100644 --- a/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java +++ b/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java @@ -2805,6 +2805,41 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest properties); } + /** + * Create some nodes that have the no cm:name and use associations that enforce uniqueness. + *

+ * ALF-5001: cm:name uniqueness check can fail if the property is not set + */ + public void testDuplicateAssocsWithoutSuppliedName() throws Throwable + { + Map properties = Collections.emptyMap(); + NodeRef parentRef = nodeService.createNode( + rootNodeRef, + ASSOC_TYPE_QNAME_TEST_CHILDREN, + QName.createQName("parent_child"), + ContentModel.TYPE_CONTAINER).getChildRef(); + ChildAssociationRef pathARef = nodeService.createNode( + parentRef, + ASSOC_TYPE_QNAME_TEST_CONTAINS, + QName.createQName("pathA"), + ContentModel.TYPE_CONTENT, + properties); + // Add the node to the same parent again + try + { + ChildAssociationRef pathBRef = nodeService.addChild( + parentRef, + pathARef.getChildRef(), + ASSOC_TYPE_QNAME_TEST_CONTAINS, + QName.createQName("pathB")); + fail("Re-added node to parent when cm:name was not set; it should have failed."); + } + catch (DuplicateChildNodeNameException e) + { + // Expected + } + } + /** * Checks that the unique constraint doesn't break delete and create within the same * transaction. diff --git a/source/java/org/alfresco/repo/node/index/NodeIndexer.java b/source/java/org/alfresco/repo/node/index/NodeIndexer.java index 7628954881..d4284c04d9 100644 --- a/source/java/org/alfresco/repo/node/index/NodeIndexer.java +++ b/source/java/org/alfresco/repo/node/index/NodeIndexer.java @@ -86,7 +86,7 @@ public class NodeIndexer { if (logger.isDebugEnabled()) { - logger.debug("indexCreateNode", new Exception("Stack Trace")); + logger.debug("indexCreateNode: " + childAssocRef, new Exception("Stack Trace")); } indexer.createNode(childAssocRef); } @@ -98,7 +98,7 @@ public class NodeIndexer { if (logger.isDebugEnabled()) { - logger.debug("indexUpdateNode", new Exception("Stack Trace")); + logger.debug("indexUpdateNode: " + nodeRef, new Exception("Stack Trace")); } indexer.updateNode(nodeRef); } @@ -110,7 +110,7 @@ public class NodeIndexer { if (logger.isDebugEnabled()) { - logger.debug("indexDeleteNode", new Exception("Stack Trace")); + logger.debug("indexDeleteNode: " + childAssocRef, new Exception("Stack Trace")); } indexer.deleteNode(childAssocRef); } @@ -122,7 +122,7 @@ public class NodeIndexer { if (logger.isDebugEnabled()) { - logger.debug("indexCreateChildAssociation", new Exception("Stack Trace")); + logger.debug("indexCreateChildAssociation: " + childAssocRef, new Exception("Stack Trace")); } indexer.createChildRelationship(childAssocRef); } @@ -134,7 +134,7 @@ public class NodeIndexer { if (logger.isDebugEnabled()) { - logger.debug("indexDeleteChildAssociation", new Exception("Stack Trace")); + logger.debug("indexDeleteChildAssociation: " + childAssocRef, new Exception("Stack Trace")); } indexer.deleteChildRelationship(childAssocRef); } @@ -146,7 +146,7 @@ public class NodeIndexer { if (logger.isDebugEnabled()) { - logger.debug("indexUpdateChildAssociation", new Exception("Stack Trace")); + logger.debug("indexUpdateChildAssociation: " + oldChildAssocRef + " -> " + newChildAssocRef, new Exception("Stack Trace")); } indexer.updateChildRelationship(oldChildAssocRef, newChildAssocRef); } diff --git a/source/java/org/alfresco/repo/ownable/impl/OwnableServiceImpl.java b/source/java/org/alfresco/repo/ownable/impl/OwnableServiceImpl.java index 99ee36622a..41a5d05a61 100644 --- a/source/java/org/alfresco/repo/ownable/impl/OwnableServiceImpl.java +++ b/source/java/org/alfresco/repo/ownable/impl/OwnableServiceImpl.java @@ -42,6 +42,7 @@ import org.alfresco.service.cmr.security.OwnableService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.EqualsHelper; +import org.alfresco.util.PropertyCheck; import org.springframework.beans.factory.InitializingBean; /** @@ -49,15 +50,16 @@ import org.springframework.beans.factory.InitializingBean; * * @author Andy Hind */ -public class OwnableServiceImpl implements OwnableService, InitializingBean, NodeServicePolicies.OnAddAspectPolicy, NodeServicePolicies.OnUpdatePropertiesPolicy, - NodeServicePolicies.OnRemoveAspectPolicy, NodeServicePolicies.OnDeleteNodePolicy +public class OwnableServiceImpl implements + OwnableService, InitializingBean, + NodeServicePolicies.OnAddAspectPolicy, + NodeServicePolicies.OnUpdatePropertiesPolicy, + NodeServicePolicies.OnRemoveAspectPolicy, + NodeServicePolicies.OnDeleteNodePolicy { private NodeService nodeService; - private AuthenticationService authenticationService; - private SimpleCache nodeOwnerCache; - private PolicyComponent policyComponent; public OwnableServiceImpl() @@ -93,41 +95,55 @@ public class OwnableServiceImpl implements OwnableService, InitializingBean, Nod public void afterPropertiesSet() throws Exception { - if (nodeService == null) - { - throw new IllegalArgumentException("Property 'nodeService' has not been set"); - } - if (authenticationService == null) - { - throw new IllegalArgumentException("Property 'authenticationService' has not been set"); - } - if (nodeOwnerCache == null) - { - throw new IllegalArgumentException("Property 'nodeOwnerCache' has not been set"); - } - if (policyComponent == null) - { - throw new IllegalArgumentException("Property 'policyComponent' has not been set"); - } + PropertyCheck.mandatory(this, "nodeService", nodeService); + PropertyCheck.mandatory(this, "authenticationService", authenticationService); + PropertyCheck.mandatory(this, "nodeOwnerCache", nodeOwnerCache); + PropertyCheck.mandatory(this, "policyComponent", policyComponent); } public void init() { - policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"), ContentModel.ASPECT_OWNABLE, new JavaBehaviour(this, "onAddAspect")); - policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"), ContentModel.ASPECT_OWNABLE, new JavaBehaviour(this, "onUpdateProperties")); - policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onRemoveAspect"), ContentModel.ASPECT_OWNABLE, new JavaBehaviour(this, - "onRemoveAspect")); - policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteNode"), ContentModel.ASPECT_OWNABLE, new JavaBehaviour(this, "onDeleteNode")); + policyComponent.bindClassBehaviour( + NodeServicePolicies.OnAddAspectPolicy.QNAME, + ContentModel.ASPECT_OWNABLE, + new JavaBehaviour(this, "onAddAspect")); + policyComponent.bindClassBehaviour( + NodeServicePolicies.OnUpdatePropertiesPolicy.QNAME, + ContentModel.ASPECT_OWNABLE, + new JavaBehaviour(this, "onUpdateProperties")); + policyComponent.bindClassBehaviour( + NodeServicePolicies.OnRemoveAspectPolicy.QNAME, + ContentModel.ASPECT_OWNABLE, + new JavaBehaviour(this, "onRemoveAspect")); + policyComponent.bindClassBehaviour( + NodeServicePolicies.OnDeleteNodePolicy.QNAME, + ContentModel.ASPECT_OWNABLE, + new JavaBehaviour(this, "onDeleteNode")); - policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"), ContentModel.ASPECT_AUDITABLE, new JavaBehaviour(this, "onAddAspect")); - policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"), ContentModel.ASPECT_AUDITABLE, new JavaBehaviour(this, "onUpdateProperties")); - policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onRemoveAspect"), ContentModel.ASPECT_AUDITABLE, new JavaBehaviour(this, - "onRemoveAspect")); - policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteNode"), ContentModel.ASPECT_AUDITABLE, new JavaBehaviour(this, "onDeleteNode")); + policyComponent.bindClassBehaviour( + NodeServicePolicies.OnAddAspectPolicy.QNAME, + ContentModel.ASPECT_AUDITABLE, + new JavaBehaviour(this, "onAddAspect")); + policyComponent.bindClassBehaviour( + NodeServicePolicies.OnUpdatePropertiesPolicy.QNAME, + ContentModel.ASPECT_AUDITABLE, + new JavaBehaviour(this, "onUpdateProperties")); + policyComponent.bindClassBehaviour( + NodeServicePolicies.OnRemoveAspectPolicy.QNAME, + ContentModel.ASPECT_AUDITABLE, + new JavaBehaviour(this, "onRemoveAspect")); + policyComponent.bindClassBehaviour( + NodeServicePolicies.OnDeleteNodePolicy.QNAME, + ContentModel.ASPECT_AUDITABLE, + new JavaBehaviour(this, "onDeleteNode")); - policyComponent.bindClassBehaviour(CopyServicePolicies.OnCopyNodePolicy.QNAME, ContentModel.ASPECT_OWNABLE, + policyComponent.bindClassBehaviour( + CopyServicePolicies.OnCopyNodePolicy.QNAME, + ContentModel.ASPECT_OWNABLE, new JavaBehaviour(this, "onCopyNode", NotificationFrequency.EVERY_EVENT)); - policyComponent.bindClassBehaviour(CopyServicePolicies.OnCopyNodePolicy.QNAME, ContentModel.ASPECT_AUDITABLE, + policyComponent.bindClassBehaviour( + CopyServicePolicies.OnCopyNodePolicy.QNAME, + ContentModel.ASPECT_AUDITABLE, new JavaBehaviour(this, "onCopyNode", NotificationFrequency.EVERY_EVENT)); } diff --git a/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java b/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java index 765db6e2da..fcbb51ebd9 100644 --- a/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java +++ b/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import javax.transaction.UserTransaction; @@ -69,6 +70,7 @@ import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.lock.LockService; import org.alfresco.service.cmr.lock.LockStatus; import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentService; @@ -362,8 +364,7 @@ public class RuleServiceCoverageTest extends TestCase // System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef)); } - public void testModifyNameTriggersInboundRule() - throws Exception + public void testCheckThatModifyNameDoesNotTriggerInboundRule() throws Exception { //this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_LOCKABLE, null); Map folderProps = new HashMap(1); @@ -402,7 +403,115 @@ public class RuleServiceCoverageTest extends TestCase // Use the file folder to change the name of the node this.fileFolderService.rename(newNodeRef, "myNewName.txt"); assertFalse(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + } + + public void testCheckThatModifyNameDoesNotTriggerOutboundRule() throws Exception + { + Map folderProps = new HashMap(1); + folderProps.put(ContentModel.PROP_NAME, "myTestFolder"); + NodeRef folder = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_FOLDER, + folderProps).getChildRef(); + Map params = new HashMap(1); + params.put(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); + + Rule rule = createRule( + RuleType.OUTBOUND, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(folder, rule); + + Map contentProps = new HashMap(1); + contentProps.put(ContentModel.PROP_NAME, "myTestDocument.txt"); + NodeRef newNodeRef = fileFolderService.create(folder, "abc.txt", ContentModel.TYPE_CONTENT).getNodeRef(); + assertFalse("Should not be versionable", nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // Use the file folder to change the name of the node + fileFolderService.rename(newNodeRef, "myNewName.txt"); + assertFalse("Should not be versionable", nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + } + + /** + * ALF-4926: Incorrect behavior of update and move rule for the same folder + *

+ * Two rules:

    + *
  • When items are deleted, copy to another folder.
  • + *
  • In addition, when items are updated, add an aspect (or any other rule).
+ * Ensure that the first copy does not result in rules being fired on the target. + */ + public void testUpdateAndMoveRuleOnSameFolder() throws Exception + { + NodeRef sourceFolder = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}sourceFolder"), + ContentModel.TYPE_FOLDER).getChildRef(); + NodeRef targetFolder = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}targetFolder"), + ContentModel.TYPE_FOLDER).getChildRef(); + + // Create UPDATE rule to add lockable aspect + Map params = new HashMap(1); + params.put("aspect-name", ContentModel.ASPECT_LOCKABLE); + Rule rule = createRule( + RuleType.UPDATE, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + this.ruleService.saveRule(sourceFolder, rule); + + // Check that the UPDATE rule works + NodeRef testNodeOneRef = fileFolderService.create(sourceFolder, "one.txt", ContentModel.TYPE_CONTENT).getNodeRef(); + assertFalse( + "Node should not have lockable aspect", + nodeService.hasAspect(testNodeOneRef, ContentModel.ASPECT_LOCKABLE)); + nodeService.setProperty(testNodeOneRef, ContentModel.PROP_LOCALE, Locale.CANADA); + assertTrue( + "Node should have lockable aspect", + nodeService.hasAspect(testNodeOneRef, ContentModel.ASPECT_LOCKABLE)); + fileFolderService.delete(testNodeOneRef); + + // Create OUTBOUND rule to copy node being deleted + params = new HashMap(1); + params.put(CopyActionExecuter.PARAM_DESTINATION_FOLDER, targetFolder); + Rule copyRule = createRule( + RuleType.OUTBOUND, + CopyActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + copyRule.applyToChildren(true); + this.ruleService.saveRule(sourceFolder, copyRule); + + // Check that this OUTBOUND rule works + NodeRef testNodeTwoRef = fileFolderService.create(sourceFolder, "two.txt", ContentModel.TYPE_CONTENT).getNodeRef(); + assertFalse( + "Node should not have lockable aspect", + nodeService.hasAspect(testNodeTwoRef, ContentModel.ASPECT_LOCKABLE)); + fileFolderService.delete(testNodeTwoRef); + assertFalse("Node was not deleted", fileFolderService.exists(testNodeTwoRef)); + assertEquals( + "There should not be any children in source folder", + 0, + fileFolderService.listFiles(sourceFolder).size()); + List targetFolderFileList = fileFolderService.listFiles(targetFolder); + assertEquals( + "Node should have been copied to target folder", + 1, + targetFolderFileList.size()); + assertFalse( + "The node copy should not be lockable", + nodeService.hasAspect(targetFolderFileList.get(0).getNodeRef(), ContentModel.ASPECT_LOCKABLE)); } public void testDisableIndividualRules() @@ -531,7 +640,7 @@ public class RuleServiceCoverageTest extends TestCase addContentToNode(contentToCopy); Map params = new HashMap(1); - params.put("aspect-name", ContentModel.ASPECT_TEMPLATABLE); + params.put(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_TEMPLATABLE); Rule rule = createRule( RuleType.INBOUND, @@ -795,8 +904,6 @@ public class RuleServiceCoverageTest extends TestCase { Map params = new HashMap(1); params.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, this.rootNodeRef); - params.put(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CHILDREN); - params.put(MoveActionExecuter.PARAM_ASSOC_QNAME, QName.createQName(TEST_NAMESPACE, "copy")); Rule rule = createRule( RuleType.INBOUND, @@ -830,7 +937,7 @@ public class RuleServiceCoverageTest extends TestCase // Check that the created node has been copied List copyChildAssocRefs = this.nodeService.getChildAssocs( this.rootNodeRef, - RegexQNamePattern.MATCH_ALL, QName.createQName(TEST_NAMESPACE, "copy")); + RegexQNamePattern.MATCH_ALL, QName.createQName(TEST_NAMESPACE, "origional")); assertNotNull(copyChildAssocRefs); // ********************************** @@ -1014,8 +1121,6 @@ public class RuleServiceCoverageTest extends TestCase { Map params = new HashMap(1); params.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, this.rootNodeRef); - params.put(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CHILDREN); - params.put(MoveActionExecuter.PARAM_ASSOC_QNAME, QName.createQName(TEST_NAMESPACE, "copy")); Rule rule = createRule( RuleType.INBOUND, @@ -1046,7 +1151,7 @@ public class RuleServiceCoverageTest extends TestCase // Check that the created node is in the new location List copyChildAssocRefs = this.nodeService.getChildAssocs( this.rootNodeRef, - RegexQNamePattern.MATCH_ALL, QName.createQName(TEST_NAMESPACE, "copy")); + RegexQNamePattern.MATCH_ALL, QName.createQName(TEST_NAMESPACE, "origional")); assertNotNull(copyChildAssocRefs); assertEquals(1, copyChildAssocRefs.size()); NodeRef movedNodeRef = copyChildAssocRefs.get(0).getChildRef(); @@ -1122,7 +1227,6 @@ public class RuleServiceCoverageTest extends TestCase * condition: no-condition() * action: checkin() */ - @SuppressWarnings("unchecked") public void testCheckInAction() { Map params = new HashMap(1); diff --git a/source/java/org/alfresco/repo/rule/RuleServiceImpl.java b/source/java/org/alfresco/repo/rule/RuleServiceImpl.java index 50c6320abc..9ecdd1780e 100644 --- a/source/java/org/alfresco/repo/rule/RuleServiceImpl.java +++ b/source/java/org/alfresco/repo/rule/RuleServiceImpl.java @@ -35,6 +35,7 @@ import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.TransactionListener; import org.alfresco.service.cmr.action.Action; @@ -472,40 +473,47 @@ public class RuleServiceImpl /** * @see org.alfresco.repo.rule.RuleService#getRulesByRuleType(org.alfresco.repo.ref.NodeRef, org.alfresco.repo.rule.RuleType) */ - public List getRules(NodeRef nodeRef, boolean includeInherited, String ruleTypeName) + public List getRules(final NodeRef nodeRef, final boolean includeInherited, final String ruleTypeName) { - List rules = new ArrayList(); + //Run from system user: https://issues.alfresco.com/jira/browse/ALF-607 + return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork>() + { - if (!this.runtimeNodeService.exists(nodeRef) || !checkNodeType(nodeRef)) - { - // Node has gone or is not the correct type - return rules; - } - if (includeInherited == true && this.runtimeNodeService.hasAspect(nodeRef, RuleModel.ASPECT_IGNORE_INHERITED_RULES) == false) - { - // Get any inherited rules - for (Rule rule : getInheritedRules(nodeRef, ruleTypeName, null)) + public List doWork() throws Exception { - // Ensure rules are not duplicated in the list - if (rules.contains(rule) == false) + List rules = new ArrayList(); + + if (!runtimeNodeService.exists(nodeRef) || !checkNodeType(nodeRef)) { - rules.add(rule); + // Node has gone or is not the correct type + return rules; + } + if (includeInherited == true && runtimeNodeService.hasAspect(nodeRef, RuleModel.ASPECT_IGNORE_INHERITED_RULES) == false) + { + // Get any inherited rules + for (Rule rule : getInheritedRules(nodeRef, ruleTypeName, null)) + { + // Ensure rules are not duplicated in the list + if (rules.contains(rule) == false) + { + rules.add(rule); + } + } } - } - } - // Get the node's own rules and add them to the list - List nodeRules = getRulesForNode(nodeRef); - for (Rule rule : nodeRules) - { - if ((rules.contains(rule) == false) && - (ruleTypeName == null || rule.getRuleTypes().contains(ruleTypeName) == true)) - { - rules.add(rule); - } - } + // Get the node's own rules and add them to the list + List nodeRules = getRulesForNode(nodeRef); + for (Rule rule : nodeRules) + { + if ((rules.contains(rule) == false) && (ruleTypeName == null || rule.getRuleTypes().contains(ruleTypeName) == true)) + { + rules.add(rule); + } + } - return rules; + return rules; + } + }, AuthenticationUtil.getSystemUserName()); } private List getRulesForNode(NodeRef nodeRef) @@ -1162,9 +1170,17 @@ public class RuleServiceImpl } } - //update all associations and actions - rule = getRule(ruleNodeRef); - if (executedRules == null || canExecuteRule(executedRules, actionedUponNodeRef, rule) == true) + final NodeRef finalRuleNodeRef = ruleNodeRef; + // update all associations and actions + rule = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public Rule doWork() throws Exception + { + return getRule(finalRuleNodeRef); + } + }, AuthenticationUtil.getSystemUserName()); + + if (executedRules == null || canExecuteRule(executedRules, actionedUponNodeRef, rule) == true) { executeRule(rule, actionedUponNodeRef, executedRules); } @@ -1423,17 +1439,26 @@ public class RuleServiceImpl /** * @see org.alfresco.service.cmr.rule.RuleService#getOwningNodeRef(org.alfresco.service.cmr.rule.Rule) */ - public NodeRef getOwningNodeRef(Rule rule) + public NodeRef getOwningNodeRef(final Rule rule) { - NodeRef result = null; - - NodeRef ruleNodeRef = rule.getNodeRef(); - if (ruleNodeRef != null) + // Run from system user: https://issues.alfresco.com/jira/browse/ALF-607 + return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() { - result = getOwningNodeRefRuleImpl(ruleNodeRef); - } + + public NodeRef doWork() throws Exception + { + + NodeRef result = null; - return result; + NodeRef ruleNodeRef = rule.getNodeRef(); + if (ruleNodeRef != null) + { + result = getOwningNodeRefRuleImpl(ruleNodeRef); + } + + return result; + } + }, AuthenticationUtil.getSystemUserName()); } /** @@ -1452,16 +1477,25 @@ public class RuleServiceImpl /** * @see org.alfresco.service.cmr.rule.RuleService#getOwningNodeRef(org.alfresco.service.cmr.action.Action) */ - public NodeRef getOwningNodeRef(Action action) + public NodeRef getOwningNodeRef(final Action action) { - NodeRef result = null; - NodeRef actionNodeRef = action.getNodeRef(); - if (actionNodeRef != null) + // Run from system user: https://issues.alfresco.com/jira/browse/ALF-607 + return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() { - result = getOwningNodeRefActionImpl(actionNodeRef); - } + + public NodeRef doWork() throws Exception + { + + NodeRef result = null; + NodeRef actionNodeRef = action.getNodeRef(); + if (actionNodeRef != null) + { + result = getOwningNodeRefActionImpl(actionNodeRef); + } - return result; + return result; + } + }, AuthenticationUtil.getSystemUserName()); } /** diff --git a/source/java/org/alfresco/repo/rule/RuleServiceImplTest.java b/source/java/org/alfresco/repo/rule/RuleServiceImplTest.java index 88461c0704..2cd7aa0abd 100644 --- a/source/java/org/alfresco/repo/rule/RuleServiceImplTest.java +++ b/source/java/org/alfresco/repo/rule/RuleServiceImplTest.java @@ -21,12 +21,15 @@ package org.alfresco.repo.rule; import java.io.File; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import org.alfresco.model.ContentModel; import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; +import org.alfresco.repo.action.executer.AddFeaturesActionExecuter; import org.alfresco.repo.action.executer.ImageTransformActionExecuter; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.transform.AbstractContentTransformerTest; @@ -48,6 +51,7 @@ import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.GUID; /** @@ -1000,4 +1004,69 @@ public class RuleServiceImplTest extends BaseRuleTest } }, false, true); } + + public void testPermissionsForPropagatedRules_ALF_8408() throws Exception + { + // Create parent and child folders + NodeRef parentNodeRef = this.nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("parentnode" + GUID.generate()), ContentModel.TYPE_FOLDER) + .getChildRef(); + + NodeRef childNodeRef = this.nodeService.createNode(parentNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("childnode" + GUID.generate()), ContentModel.TYPE_FOLDER) + .getChildRef(); + + // Remove all permissions for parent + permissionService.deletePermissions(parentNodeRef); + permissionService.setInheritParentPermissions(parentNodeRef, false); + + // Create test user + String username = "ruleTestUser" + GUID.generate(); + this.authenticationService.createAuthentication(username, "password".toCharArray()); + + // Set user permissions for child node + permissionService.deletePermissions(childNodeRef); + permissionService.setInheritParentPermissions(childNodeRef, false); + permissionService.setPermission(childNodeRef, username, PermissionService.CONTRIBUTOR, true); + + // Create rule for child node + Rule testRule = new Rule(); + testRule.setRuleTypes(Collections.singletonList(RuleType.INBOUND)); + testRule.setTitle("RuleServiceTest" + GUID.generate()); + testRule.setDescription(DESCRIPTION); + testRule.applyToChildren(true); + Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); + testRule.setAction(action); + this.ruleService.saveRule(parentNodeRef, testRule); + assertNotNull("Rule was not saved", testRule.getNodeRef()); + + // Authenticate as test user + this.authenticationService.authenticate(username, "password".toCharArray()); + authenticationComponent.setCurrentUser(username); + + // Search rules + List rules = this.ruleService.getRules(childNodeRef, true, testRule.getRuleTypes().get(0)); + assertNotNull("No rules found", rules); + assertTrue("Created rule is not found", new HashSet(rules).contains(testRule)); + + // New node + NodeRef actionedUponNodeRef = this.nodeService.createNode(childNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("actioneduponnode" + GUID.generate()), + ContentModel.TYPE_CONTENT).getChildRef(); + + // Testing immediate rule execution + if (this.nodeService.hasAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE)) + { + this.nodeService.removeAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE); + } + ((RuntimeRuleService) ruleService).executeRule(testRule, actionedUponNodeRef, null); + assertTrue("Rule was not executed", this.nodeService.hasAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // Queue the rule to be executed later and execute pending rules + if (this.nodeService.hasAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE)) + { + this.nodeService.removeAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE); + } + ((RuntimeRuleService) ruleService).addRulePendingExecution(parentNodeRef, actionedUponNodeRef, testRule); + ((RuntimeRuleService) ruleService).executePendingRules(); + assertTrue("Pending rule was not executed", this.nodeService.hasAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE)); + } } diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/BeforeDeleteChildAssociationRuleTrigger.java b/source/java/org/alfresco/repo/rule/ruletrigger/BeforeDeleteChildAssociationRuleTrigger.java index fa5f936fdf..43ea840b44 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/BeforeDeleteChildAssociationRuleTrigger.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/BeforeDeleteChildAssociationRuleTrigger.java @@ -18,9 +18,13 @@ */ package org.alfresco.repo.rule.ruletrigger; +import java.util.Set; + import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.transaction.TransactionalResourceHelper; import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; @@ -78,11 +82,20 @@ public class BeforeDeleteChildAssociationRuleTrigger public void beforeDeleteChildAssociation(ChildAssociationRef childAssocRef) { + NodeRef childNodeRef = childAssocRef.getChildRef(); + + // Avoid renamed nodes + Set renamedNodeRefSet = TransactionalResourceHelper.getSet(RULE_TRIGGER_RENAMED_NODES); + if (renamedNodeRefSet.contains(childNodeRef)) + { + return; + } + if (logger.isDebugEnabled() == true) { logger.debug("Single child assoc trigger (policy = " + POLICY + ") fired for parent node " + childAssocRef.getParentRef() + " and child node " + childAssocRef.getChildRef()); } - triggerRules(childAssocRef.getParentRef(), childAssocRef.getChildRef()); + triggerRules(childAssocRef.getParentRef(), childNodeRef); } } diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/CreateNodeRuleTrigger.java b/source/java/org/alfresco/repo/rule/ruletrigger/CreateNodeRuleTrigger.java index 1dd2bbe2d6..23de4a3c76 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/CreateNodeRuleTrigger.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/CreateNodeRuleTrigger.java @@ -18,10 +18,13 @@ */ package org.alfresco.repo.rule.ruletrigger; +import java.util.Set; + import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.Behaviour.NotificationFrequency; import org.alfresco.repo.rule.RuntimeRuleService; +import org.alfresco.repo.transaction.TransactionalResourceHelper; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.NamespaceService; @@ -109,8 +112,13 @@ public class CreateNodeRuleTrigger extends RuleTriggerAbstractBase * {@inheritDoc} */ public void onCreateNode(ChildAssociationRef childAssocRef) - { + { NodeRef nodeRef = childAssocRef.getChildRef(); + + // Keep track of new nodes to prevent firing of updates in the same transaction + Set newNodeRefSet = TransactionalResourceHelper.getSet(RULE_TRIGGER_NEW_NODES); + newNodeRefSet.add(nodeRef); + if (nodeRef != null && nodeService.exists(nodeRef) == true && nodeService.hasAspect(nodeRef, ASPECT_NO_CONTENT) == false) diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/OnContentUpdateRuleTrigger.java b/source/java/org/alfresco/repo/rule/ruletrigger/OnContentUpdateRuleTrigger.java index 42910cb045..74dbac355a 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/OnContentUpdateRuleTrigger.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/OnContentUpdateRuleTrigger.java @@ -19,11 +19,13 @@ package org.alfresco.repo.rule.ruletrigger; import java.util.List; +import java.util.Set; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.ContentServicePolicies; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.transaction.TransactionalResourceHelper; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.NodeRef; @@ -112,6 +114,18 @@ public class OnContentUpdateRuleTrigger extends RuleTriggerAbstractBase } } } + + // Double check for content created in this transaction + if (fail == false && !newContent) + { + Set newNodeRefSet = TransactionalResourceHelper.getSet(RULE_TRIGGER_NEW_NODES); + boolean wasCreatedInTxn = newNodeRefSet.contains(nodeRef); + if (logger.isDebugEnabled() && wasCreatedInTxn) + { + logger.debug("Receiving content property update for node created in transaction: " + nodeRef); + } + fail = wasCreatedInTxn; + } // Trigger the rules in the appropriate way if (fail == false && newContent == this.onNewContent) diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/OnCreateChildAssociationRuleTrigger.java b/source/java/org/alfresco/repo/rule/ruletrigger/OnCreateChildAssociationRuleTrigger.java index 2344831b1e..96596e760b 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/OnCreateChildAssociationRuleTrigger.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/OnCreateChildAssociationRuleTrigger.java @@ -24,6 +24,7 @@ import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.transaction.TransactionalResourceHelper; import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; @@ -80,31 +81,26 @@ public class OnCreateChildAssociationRuleTrigger public void onCreateChildAssociation(ChildAssociationRef childAssocRef, boolean isNewNode) { + // Avoid new nodes if (isNewNode) { return; } + + NodeRef childNodeRef = childAssocRef.getChildRef(); + + // Avoid renamed nodes + Set renamedNodeRefSet = TransactionalResourceHelper.getSet(RULE_TRIGGER_RENAMED_NODES); + if (renamedNodeRefSet.contains(childNodeRef)) + { + return; + } + if (logger.isDebugEnabled() == true) { logger.debug("Single child assoc trigger (policy = " + POLICY_NAME + ") fired for parent node " + childAssocRef.getParentRef() + " and child node " + childAssocRef.getChildRef()); } - // NOTE: - // - // We check for the presence of this resource in the transaction to determine whether a rename has been issued. If that is the case - // then we don't want to trigger any associated rules. - // - // See http://issues.alfresco.com/browse/AR-1544 - Set nodeRefRenameSet = TransactionalResourceHelper.getSet(RULE_TRIGGER_NODESET); - String marker = childAssocRef.getChildRef().toString()+"rename"; - if (!nodeRefRenameSet.contains(marker)) - { - triggerRules(childAssocRef.getParentRef(), childAssocRef.getChildRef()); - } - else - { - // Remove the marker - nodeRefRenameSet.remove(marker); - } + triggerRules(childAssocRef.getParentRef(), childNodeRef); } } diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/OnPropertyUpdateRuleTrigger.java b/source/java/org/alfresco/repo/rule/ruletrigger/OnPropertyUpdateRuleTrigger.java index de62be9923..a1f6084da9 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/OnPropertyUpdateRuleTrigger.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/OnPropertyUpdateRuleTrigger.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.alfresco.model.ContentModel; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.transaction.TransactionalResourceHelper; @@ -120,37 +121,54 @@ public class OnPropertyUpdateRuleTrigger extends RuleTriggerAbstractBase } /** - * @see org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy#onUpdateProperties(org.alfresco.service.cmr.repository.NodeRef, java.util.Map, java.util.Map) + * Triggers rules if properties have been updated */ public void onUpdateProperties(NodeRef nodeRef, Map before, Map after) { - if (logger.isDebugEnabled() == true) + // Do not fire if the node has been created in this transaction + Set newNodeRefSet = TransactionalResourceHelper.getSet(RULE_TRIGGER_NEW_NODES); + boolean wasCreatedInTxn = newNodeRefSet.contains(nodeRef); + if (logger.isDebugEnabled() && wasCreatedInTxn) { - logger.debug("OnPropertyUpdate rule triggered fired; nodeRef=" + nodeRef.toString() + "; triggerParentRules=" + this.triggerParentRules); + logger.debug("Receiving property update for node created in transaction: " + nodeRef); } - Set nodeRefSet = TransactionalResourceHelper.getSet(RULE_TRIGGER_NODESET); - // Only try and trigger the rules if a non protected property has been modified - if (!nodeRefSet.contains(nodeRef.toString()) && + if (!wasCreatedInTxn && before.size() != 0 && // ALF-4846: Do not trigger for newly created nodes havePropertiesBeenModified(nodeRef, before, after) == true) { + // Keep track of name changes explicitly. This prevents the later association change from + // triggering 'inbound' rules + if (!EqualsHelper.nullSafeEquals(before.get(ContentModel.PROP_NAME), after.get(ContentModel.PROP_NAME))) + { + // Name has changed + Set renamedNodeRefSet = TransactionalResourceHelper.getSet(RULE_TRIGGER_RENAMED_NODES); + renamedNodeRefSet.add(nodeRef); + } + if (triggerParentRules == true) { List parentsAssocRefs = this.nodeService.getParentAssocs(nodeRef); for (ChildAssociationRef parentAssocRef : parentsAssocRefs) { triggerRules(parentAssocRef.getParentRef(), nodeRef); + if (logger.isDebugEnabled() == true) + { + logger.debug( + "OnPropertyUpdate rule triggered (parent); " + + "nodeRef=" + parentAssocRef.getParentRef()); + } } } else { triggerRules(nodeRef, nodeRef); + if (logger.isDebugEnabled() == true) + { + logger.debug("OnPropertyUpdate rule triggered; nodeRef=" + nodeRef); + } } } } - - - } diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/RuleTrigger.java b/source/java/org/alfresco/repo/rule/ruletrigger/RuleTrigger.java index a295b5e5df..31e8e83aec 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/RuleTrigger.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/RuleTrigger.java @@ -27,8 +27,10 @@ import org.alfresco.service.cmr.rule.RuleType; */ public interface RuleTrigger { - /** Key to look up a Set of String values controlling the firing of rules */ - public static final String RULE_TRIGGER_NODESET = "RuleTrigger.NodeSet"; + /** Key to store newly-created nodes for the controlling of rule triggers */ + public static final String RULE_TRIGGER_NEW_NODES = "RuleTrigger.NewNodes"; + /** Key to store renamed nodes for the controlling of rule triggers */ + public static final String RULE_TRIGGER_RENAMED_NODES = "RuleTrigger.RenamedNodes"; /** * Register the rule trigger diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerTest.java b/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerTest.java index 19e47c1571..cf3a995025 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerTest.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerTest.java @@ -20,6 +20,8 @@ package org.alfresco.repo.rule.ruletrigger; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; @@ -53,14 +55,23 @@ public class RuleTriggerTest extends BaseSpringTest @Override protected void onSetUpInTransaction() throws Exception { - this.nodeService = (NodeService)this.applicationContext.getBean("nodeService"); - this.contentService = (ContentService)this.applicationContext.getBean("contentService"); + ServiceRegistry serviceRegistry = (ServiceRegistry) applicationContext.getBean(ServiceRegistry.SERVICE_REGISTRY); + this.nodeService = serviceRegistry.getNodeService(); + this.contentService = serviceRegistry.getContentService(); + + AuthenticationUtil.setRunAsUser(AuthenticationUtil.getSystemUserName()); this.testStoreRef = this.nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); this.rootNodeRef = this.nodeService.getRootNode(this.testStoreRef); } - public void testOnCreateNodeTrigger() + @Override + protected void onTearDownInTransaction() throws Exception + { + AuthenticationUtil.clearCurrentSecurityContext(); + } + + public void testOnCreateNodeTrigger() { TestRuleType ruleType = createTestRuleType(ON_CREATE_NODE_TRIGGER); assertFalse(ruleType.rulesTriggered); @@ -233,6 +244,17 @@ public class RuleTriggerTest extends BaseSpringTest // Check to see if the rule type has been triggered assertTrue(contentCreate.rulesTriggered); + + // Try and trigger the type (again) + contentCreate.rulesTriggered = false; + assertFalse(contentCreate.rulesTriggered); + ContentWriter contentWriter2 = this.contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); + contentWriter2.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + contentWriter2.setEncoding("UTF-8"); + contentWriter2.putContent("some content"); + + // Check to see if the rule type has been triggered + assertFalse(contentCreate.rulesTriggered); } public void testOnContentUpdateTrigger() @@ -243,7 +265,9 @@ public class RuleTriggerTest extends BaseSpringTest ContentModel.ASSOC_CHILDREN, ContentModel.TYPE_CONTENT).getChildRef(); + TestRuleType contentCreate = createTestRuleType(ON_CONTENT_CREATE_TRIGGER); TestRuleType contentUpdate = createTestRuleType(ON_CONTENT_UPDATE_TRIGGER); + assertFalse(contentCreate.rulesTriggered); assertFalse(contentUpdate.rulesTriggered); // Try and trigger the type @@ -252,17 +276,41 @@ public class RuleTriggerTest extends BaseSpringTest contentWriter.setEncoding("UTF-8"); contentWriter.putContent("some content"); - // Check to see if the rule type has been triggered + // Check to see if the rule type has been triggered + assertTrue(contentCreate.rulesTriggered); assertFalse(contentUpdate.rulesTriggered); - // Try and trigger the type + // Try and trigger the type (again) + contentCreate.rulesTriggered = false; + assertFalse(contentCreate.rulesTriggered); ContentWriter contentWriter2 = this.contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); contentWriter2.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); contentWriter2.setEncoding("UTF-8"); contentWriter2.putContent("more content some content"); // Check to see if the rule type has been triggered - assertTrue(contentUpdate.rulesTriggered); + assertFalse(contentCreate.rulesTriggered); + assertFalse( + "Content update must not fire if the content was created in the same txn.", + contentUpdate.rulesTriggered); + + // Terminate the transaction + setComplete(); + endTransaction(); + + // Try and trigger the type (again) + ContentWriter contentWriter3 = this.contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); + contentWriter3.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + contentWriter3.setEncoding("UTF-8"); + contentWriter3.putContent("Yet content some content"); + + // Check to see if the rule type has been triggered + assertFalse( + "Content create should not be fired on an update in a new txn", + contentCreate.rulesTriggered); + assertTrue( + "Content update must not fire if the content was created in the same txn.", + contentUpdate.rulesTriggered); } private TestRuleType createTestRuleType(String ruleTriggerName) diff --git a/source/java/org/alfresco/repo/search/AVMSnapShotTriggeredIndexingMethodInterceptor.java b/source/java/org/alfresco/repo/search/AVMSnapShotTriggeredIndexingMethodInterceptor.java index ae822d1414..4f391715b8 100644 --- a/source/java/org/alfresco/repo/search/AVMSnapShotTriggeredIndexingMethodInterceptor.java +++ b/source/java/org/alfresco/repo/search/AVMSnapShotTriggeredIndexingMethodInterceptor.java @@ -271,6 +271,8 @@ public class AVMSnapShotTriggeredIndexingMethodInterceptor implements MethodInte if ((last == -1) && (! hasIndexBeenCreated(store))) { createIndex(store); + // ALF-7845 + last = getLastIndexedSnapshot(avmIndexer, store); } int from = before != -1 ? before : last; diff --git a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java index 414843e7bb..d5889087f8 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java @@ -219,6 +219,7 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp NodeRef ref = new NodeRef(id); deleteImpl(ref.toString(), IndexDeleteMode.DELETE, true, mainReader); } + td.close(); } catch (IOException e) { @@ -1083,7 +1084,7 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp } // locale free identifiers are in the default field - doc.add(new Field(attributeName, t.termText(), Field.Store.NO, Field.Index.NO_NORMS, Field.TermVector.NO)); + doc.add(new Field(attributeName, t.termText(), fieldStore, Field.Index.NO_NORMS, Field.TermVector.NO)); } } @@ -1195,7 +1196,7 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp { doc.add(new Field(attributeName + "." + localeText + ".sort", t.termText(), Field.Store.NO, Field.Index.NO_NORMS, Field.TermVector.NO)); } - doc.add(new Field(attributeName, t.termText(), Field.Store.NO, Field.Index.NO_NORMS, Field.TermVector.NO)); + doc.add(new Field(attributeName, t.termText(), fieldStore, Field.Index.NO_NORMS, Field.TermVector.NO)); } } catch (IOException e) @@ -1265,7 +1266,7 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp try { date = df.parse(strValue); - doc.add(new Field(attributeName, df.format(date), Field.Store.NO, Field.Index.NO_NORMS, Field.TermVector.NO)); + doc.add(new Field(attributeName, df.format(date), fieldStore, Field.Index.NO_NORMS, Field.TermVector.NO)); } catch (ParseException e) { diff --git a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java index 136f3e3ac8..b6277a03e8 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java @@ -103,6 +103,10 @@ import org.alfresco.util.ISO9075; import org.alfresco.util.CachingDateFormat.SimpleDateFormatAndResolution; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.Term; +import org.apache.lucene.index.TermDocs; +import org.apache.lucene.index.TermEnum; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.PostgreSQLDialect; import org.springframework.context.ApplicationContext; @@ -216,9 +220,9 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener private QueryEngine queryEngine; private NodeRef n15; - + private M2Model model; - + // TODO: pending replacement private Dialect dialect; @@ -238,17 +242,14 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener super(arg0); } - public void afterDictionaryDestroy() { } - public void afterDictionaryInit() { } - public void onDictionaryInit() { // Register the test model @@ -256,11 +257,10 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener namespaceDao.addPrefix("test", TEST_NAMESPACE); } - public void setUp() throws Exception { dialect = (Dialect) ctx.getBean("dialect"); - + nodeService = (NodeService) ctx.getBean("dbNodeService"); dictionaryService = (DictionaryService) ctx.getBean("dictionaryService"); dictionaryDAO = (DictionaryDAO) ctx.getBean("dictionaryDAO"); @@ -297,11 +297,11 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener ClassLoader cl = BaseNodeServiceTest.class.getClassLoader(); InputStream modelStream = cl.getResourceAsStream("org/alfresco/repo/search/impl/lucene/LuceneTest_model.xml"); assertNotNull(modelStream); - model = M2Model.createModel(modelStream); + model = M2Model.createModel(modelStream); dictionaryDAO.register(this); dictionaryDAO.reset(); assertNotNull(dictionaryDAO.getClass(testSuperType)); - + StoreRef storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); rootNodeRef = nodeService.getRootNode(storeRef); @@ -482,7 +482,7 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener // note: cm:thumbnail - hence auditable aspect will be applied with mandatory properties (cm:created, cm:modified, cm:creator, cm:modifier) n15 = nodeService.createNode(n13, ASSOC_TYPE_QNAME, QName.createQName("{namespace}fifteen"), ContentModel.TYPE_THUMBNAIL, getOrderProperties()).getChildRef(); - + ContentWriter writer = contentService.getWriter(n14, ContentModel.PROP_CONTENT, true); writer.setEncoding("UTF-8"); // InputStream is = @@ -837,6 +837,168 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener } + private IndexReader getIndexReader() + { + ADMLuceneSearcherImpl searcher = ADMLuceneSearcherImpl.getSearcher(rootNodeRef.getStoreRef(), indexerAndSearcher); + searcher.setNodeService(nodeService); + searcher.setDictionaryService(dictionaryService); + searcher.setTenantService(tenantService); + searcher.setNamespacePrefixResolver(getNamespacePrefixResolver("namespace")); + searcher.setQueryRegister(queryRegisterComponent); + searcher.setQueryLanguages(((AbstractLuceneIndexerAndSearcherFactory) indexerAndSearcher).queryLanguages); + + return searcher.getSearcher().getIndexReader(); + } + + public void testMaskDeletes() throws Exception + { + testTX.commit(); + testTX = transactionService.getUserTransaction(); + testTX.begin(); + + SearchParameters sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery("PATH:\"//.\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(true); + ResultSet results = serviceRegistry.getSearchService().query(sp); + int initialCount = results.length(); + results.close(); + + for (int j = 0; j < 20; j++) + { + ArrayList added = new ArrayList(); + for (int i = 0; i < 50; i++) + { + Map properties = new HashMap(); + properties.put(ContentModel.PROP_NAME, "Mask " + i); + added.add(nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "mask-" + i), testSuperType, + properties).getChildRef()); + } + testTX.commit(); + testTX = transactionService.getUserTransaction(); + testTX.begin(); + + int count = 0; + IndexReader indexReader = getIndexReader(); + TermDocs termDocs = indexReader.termDocs(new Term("@{http://www.alfresco.org/model/content/1.0}name", "mask")); + if (termDocs.next()) + { + count++; + while (termDocs.skipTo(termDocs.doc())) + { + count++; + } + } + termDocs.close(); + assertEquals(added.size() + j, count); + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery("PATH:\"//cm:*\" AND @cm\\:name:(0 1 2 3 4 5 6 7 8 9) AND ISNOTNULL:\"cm:name\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(true); + + results = serviceRegistry.getSearchService().query(sp); + results.close(); + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery("@cm\\:name:\"mask 1\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(true); + + results = serviceRegistry.getSearchService().query(sp); + results.close(); + + for (int i = 0; i < added.size() - 1; i++) + { + Map properties = new HashMap(); + properties.put(ContentModel.PROP_NAME, "Mask " + i); + nodeService.setProperties(added.get(i), properties); + } + + testTX.commit(); + testTX = transactionService.getUserTransaction(); + testTX.begin(); + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery("PATH:\"//cm:*\" AND @cm\\:name:(0 1 2 3 4 5 6 7 8 9) AND ISNOTNULL:\"cm:name\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(true); + + results = serviceRegistry.getSearchService().query(sp); + results.close(); + + count = 0; + indexReader = getIndexReader(); + termDocs = indexReader.termDocs(new Term("@{http://www.alfresco.org/model/content/1.0}name", "mask")); + if (termDocs.next()) + { + count++; + while (termDocs.skipTo(termDocs.doc())) + { + count++; + } + } + termDocs.close(); + assertEquals(added.size() + j, count); + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery("@cm\\:name:\"mask 1\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(true); + + results = serviceRegistry.getSearchService().query(sp); + results.close(); + + for (int i = 0; i < added.size() - 1; i++) + { + Map properties = new HashMap(); + properties.put(ContentModel.PROP_NAME, "Mask " + i); + nodeService.deleteNode(added.get(i)); + } + testTX.commit(); + testTX = transactionService.getUserTransaction(); + testTX.begin(); + + count = 0; + indexReader = getIndexReader(); + termDocs = indexReader.termDocs(new Term("@{http://www.alfresco.org/model/content/1.0}name", "mask")); + if (termDocs.next()) + { + count++; + while (termDocs.skipTo(termDocs.doc())) + { + count++; + } + } + termDocs.close(); + assertEquals(j+1, count); + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery("PATH:\"//cm:*\" AND @cm\\:name:(0 1 2 3 4 5 6 7 8 9) AND ISNOTNULL:\"cm:name\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(true); + + results = serviceRegistry.getSearchService().query(sp); + results.close(); + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery("@cm\\:name:\"mask 1\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(true); + + results = serviceRegistry.getSearchService().query(sp); + results.close(); + } + + } + public void testQuoting() throws Exception { testTX.commit(); @@ -852,11 +1014,11 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener ResultSet results = serviceRegistry.getSearchService().query(sp); results.close(); } - + public void test_ALF_8007() throws Exception { // Check that updates before and after queries do not produce duplicates - + testTX.commit(); testTX = transactionService.getUserTransaction(); testTX.begin(); @@ -875,60 +1037,57 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results = serviceRegistry.getSearchService().query(sp); assertEquals(0, results.length()); results.close(); - - + Map properties = new HashMap(); properties.put(ContentModel.PROP_NAME, "ALF-8007"); NodeRef one = nodeService.createNode(rootNodeRef, ASSOC_TYPE_QNAME, QName.createQName("{namespace}ALF-8007"), ContentModel.TYPE_CONTENT, properties).getChildRef(); - + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007\""); sp.addStore(rootNodeRef.getStoreRef()); sp.excludeDataInTheCurrentTransaction(false); - + results = serviceRegistry.getSearchService().query(sp); assertEquals(1, results.length()); results.close(); - - + MLText desc1 = new MLText(); desc1.addValue(Locale.ENGLISH, "ALF 8007"); desc1.addValue(Locale.US, "ALF 8007"); - + nodeService.setProperty(one, ContentModel.PROP_DESCRIPTION, desc1); - + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007\""); sp.addStore(rootNodeRef.getStoreRef()); sp.excludeDataInTheCurrentTransaction(false); - + results = serviceRegistry.getSearchService().query(sp); assertEquals(1, results.length()); results.close(); - + // check delete after update does delete from the index // ALF-8007 // Already seen the delete in the TX and it is skipped (should only skip deletes in the same flush) - + nodeService.deleteNode(one); - + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007\""); sp.addStore(rootNodeRef.getStoreRef()); sp.excludeDataInTheCurrentTransaction(false); - + results = serviceRegistry.getSearchService().query(sp); assertEquals(0, results.length()); results.close(); - + // Check unreported ... create, query, update, delete - // ... create, query, move, delete - - + // ... create, query, move, delete + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007-2\""); @@ -938,11 +1097,11 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results = serviceRegistry.getSearchService().query(sp); assertEquals(0, results.length()); results.close(); - + properties = new HashMap(); properties.put(ContentModel.PROP_NAME, "ALF-8007-2"); NodeRef two = nodeService.createNode(rootNodeRef, ASSOC_TYPE_QNAME, QName.createQName("{namespace}ALF-8007-2"), ContentModel.TYPE_CONTENT, properties).getChildRef(); - + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007-2\""); @@ -952,26 +1111,26 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results = serviceRegistry.getSearchService().query(sp); assertEquals(1, results.length()); results.close(); - + desc1 = new MLText(); desc1.addValue(Locale.ENGLISH, "ALF 8007 2"); desc1.addValue(Locale.US, "ALF 8007 2"); - + nodeService.setProperty(two, ContentModel.PROP_DESCRIPTION, desc1); nodeService.deleteNode(two); - + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007-2\""); sp.addStore(rootNodeRef.getStoreRef()); sp.excludeDataInTheCurrentTransaction(false); - + results = serviceRegistry.getSearchService().query(sp); assertEquals(0, results.length()); results.close(); - - // ... create, query, move, delete - + + // ... create, query, move, delete + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007-3\""); @@ -981,11 +1140,11 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results = serviceRegistry.getSearchService().query(sp); assertEquals(0, results.length()); results.close(); - + properties = new HashMap(); properties.put(ContentModel.PROP_NAME, "ALF-8007-3"); NodeRef three = nodeService.createNode(rootNodeRef, ASSOC_TYPE_QNAME, QName.createQName("{namespace}ALF-8007-3"), ContentModel.TYPE_CONTENT, properties).getChildRef(); - + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007-3\""); @@ -995,26 +1154,26 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results = serviceRegistry.getSearchService().query(sp); assertEquals(1, results.length()); results.close(); - + desc1 = new MLText(); desc1.addValue(Locale.ENGLISH, "ALF 8007 3"); desc1.addValue(Locale.US, "ALF 8007 3"); - + nodeService.moveNode(three, n1, ASSOC_TYPE_QNAME, QName.createQName("{namespace}ALF-8007-3")); nodeService.deleteNode(three); - + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007-3\""); sp.addStore(rootNodeRef.getStoreRef()); sp.excludeDataInTheCurrentTransaction(false); - + results = serviceRegistry.getSearchService().query(sp); assertEquals(0, results.length()); results.close(); - - // ... create, move, query, delete - + + // ... create, move, query, delete + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007-4\""); @@ -1024,11 +1183,11 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results = serviceRegistry.getSearchService().query(sp); assertEquals(0, results.length()); results.close(); - + properties = new HashMap(); properties.put(ContentModel.PROP_NAME, "ALF-8007-4"); NodeRef four = nodeService.createNode(rootNodeRef, ASSOC_TYPE_QNAME, QName.createQName("{namespace}ALF-8007-4"), ContentModel.TYPE_CONTENT, properties).getChildRef(); - + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007-4\""); @@ -1038,13 +1197,13 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results = serviceRegistry.getSearchService().query(sp); assertEquals(1, results.length()); results.close(); - + desc1 = new MLText(); desc1.addValue(Locale.ENGLISH, "ALF 8007 4"); desc1.addValue(Locale.US, "ALF 8007 4"); - + nodeService.moveNode(four, n1, ASSOC_TYPE_QNAME, QName.createQName("{namespace}ALF-8007-4")); - + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007-4\""); @@ -1054,21 +1213,20 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results = serviceRegistry.getSearchService().query(sp); assertEquals(1, results.length()); results.close(); - + nodeService.deleteNode(four); - + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007-4\""); sp.addStore(rootNodeRef.getStoreRef()); sp.excludeDataInTheCurrentTransaction(false); - + results = serviceRegistry.getSearchService().query(sp); assertEquals(0, results.length()); results.close(); - - } + } public void testPublicServiceSearchServicePaging() throws Exception { @@ -1498,29 +1656,29 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener ftsQueryWithCount(searcher, "brown..dog", 1); // is this allowed?? fail("Range query should not be supported against type d:content"); } - catch(UnsupportedOperationException e) + catch (UnsupportedOperationException e) { - + } - + try { ftsQueryWithCount(searcher, "TEXT:brown..dog", 1); fail("Range query should not be supported against type d:content"); } - catch(UnsupportedOperationException e) + catch (UnsupportedOperationException e) { - + } - + try { ftsQueryWithCount(searcher, "cm:content:brown..dog", 1); fail("Range query should not be supported against type d:content"); } - catch(UnsupportedOperationException e) + catch (UnsupportedOperationException e) { - + } QName qname = QName.createQName(TEST_NAMESPACE, "float\\-ista"); @@ -1695,7 +1853,7 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener assertEquals(count, results.length()); results.close(); } - + public void ftsQueryWithCount(ADMLuceneSearcherImpl searcher, String defaultFieldName, String query, int count) { SearchParameters sp = new SearchParameters(); @@ -2654,7 +2812,7 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener { public Object execute() throws Throwable { - for (int i = 0; i < 100; i+=10) + for (int i = 0; i < 100; i += 10) { HashSet refs = new HashSet(); for (int j = 0; j < i; j++) @@ -3425,7 +3583,7 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener // sort by ML text - //Locale[] testLocales = new Locale[] { I18NUtil.getLocale(), Locale.ENGLISH, Locale.FRENCH, Locale.CHINESE }; + // Locale[] testLocales = new Locale[] { I18NUtil.getLocale(), Locale.ENGLISH, Locale.FRENCH, Locale.CHINESE }; Locale[] testLocales = new Locale[] { I18NUtil.getLocale(), Locale.ENGLISH, Locale.FRENCH }; for (Locale testLocale : testLocales) { @@ -3493,7 +3651,7 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results.close(); // test sort on unkown properties ALF-4193 - + spN = new SearchParameters(); spN.addStore(rootNodeRef.getStoreRef()); spN.setLanguage(SearchService.LANGUAGE_LUCENE); @@ -3501,7 +3659,7 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener spN.addSort("PARENT", false); results = searcher.query(spN); results.close(); - + spN = new SearchParameters(); spN.addStore(rootNodeRef.getStoreRef()); spN.setLanguage(SearchService.LANGUAGE_LUCENE); @@ -3510,7 +3668,6 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results = searcher.query(spN); results.close(); - luceneFTS.resume(); @@ -4462,7 +4619,7 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener continue; } System.out.println("Date format: "+df.getSimpleDateFormat()); - + // if(usesDateTimeAnalyser && (df.getSimpleDateFormat().format(date).length() < 22)) // { // continue; @@ -4508,7 +4665,7 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener assertTrue("n14 not in results", (results.getNodeRef(0).equals(n14) || results.getNodeRef(1).equals(n14))); assertTrue("n15 not in results", (results.getNodeRef(0).equals(n15) || results.getNodeRef(1).equals(n15))); results.close(); - + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@cm\\:created:[MIN TO NOW]", null); assertEquals(2, results.length()); @@ -4889,39 +5046,40 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TYPE:\"" + testSuperType.toPrefixString(namespacePrefixResolver) + "\"", null); assertEquals(13, results.length()); results.close(); - + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TYPE:\"" + ContentModel.TYPE_CONTENT.toString() + "\"", null); assertEquals(1, results.length()); results.close(); - + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TYPE:\"cm:content\"", null); assertEquals(1, results.length()); results.close(); - + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TYPE:\"cm:CONTENT\"", null); assertEquals(1, results.length()); results.close(); - + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TYPE:\"CM:CONTENT\"", null); assertEquals(1, results.length()); results.close(); - + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TYPE:\"CONTENT\"", null); assertEquals(1, results.length()); results.close(); - + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TYPE:\"content\"", null); assertEquals(1, results.length()); results.close(); - + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TYPE:\"" + ContentModel.TYPE_THUMBNAIL.toString() + "\"", null); assertEquals(1, results.length()); results.close(); - results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TYPE:\"" + ContentModel.TYPE_THUMBNAIL.toString() + "\" TYPE:\"" + ContentModel.TYPE_CONTENT.toString() + "\"", null); + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TYPE:\"" + + ContentModel.TYPE_THUMBNAIL.toString() + "\" TYPE:\"" + ContentModel.TYPE_CONTENT.toString() + "\"", null); assertEquals(2, results.length()); results.close(); - + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "EXACTTYPE:\"" + testSuperType.toString() + "\"", null); assertEquals(12, results.length()); results.close(); @@ -6610,7 +6768,7 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener // http://archives.postgresql.org/pgsql-jdbc/2007-02/msg00115.php COMPLEX_LOCAL_NAME = "\u0020\u0060\u00ac\u00a6\u0021\"\u00a3\u0024\u0025\u005e\u0026\u002a\u0028\u0029\u002d\u005f\u003d\u002b\t\n\\\u005b\u005d\u007b\u007d\u003b\u0027\u0023\u003a\u0040\u007e\u002c\u002e\u002f\u003c\u003e\u003f\\u007c\u005f\u0078\u0054\u0036\u0035\u0041\u005f"; } - + luceneFTS.pause(); buildBaseIndex(); runBaseTests(); diff --git a/source/java/org/alfresco/repo/search/impl/lucene/FilterIndexReaderByStringId.java b/source/java/org/alfresco/repo/search/impl/lucene/FilterIndexReaderByStringId.java index 098bb88ddb..118b648e4a 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/FilterIndexReaderByStringId.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/FilterIndexReaderByStringId.java @@ -85,6 +85,7 @@ public class FilterIndexReaderByStringId extends FilterIndexReader { deletedDocuments.set(td.doc()); } + td.close(); } } else @@ -108,8 +109,8 @@ public class FilterIndexReaderByStringId extends FilterIndexReader } } } - } + // searcher does not need to be closed, the reader is live } } catch (IOException e) @@ -244,20 +245,19 @@ public class FilterIndexReaderByStringId extends FilterIndexReader public boolean skipTo(int i) throws IOException { - boolean result = in.skipTo(i); - if (result == false) + if (!in.skipTo(i)) { return false; } - if (deletedDocuments.get(in.doc())) + while (deletedDocuments.get(in.doc())) { - return skipTo(i); - } - else - { - return true; + if (!in.next()) + { + return false; + } } + return true; } public void close() throws IOException diff --git a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfoTest.java b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfoTest.java index fb977b8295..1869724540 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfoTest.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfoTest.java @@ -127,6 +127,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -144,6 +145,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -164,6 +166,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -229,6 +232,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -246,6 +250,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -266,6 +271,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -304,6 +310,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -323,6 +330,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -346,6 +354,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -418,6 +427,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -435,6 +445,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -455,6 +466,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -503,6 +515,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } for (int j = 0; j < UPDATE_LIST.length; j++) { @@ -517,6 +530,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -536,6 +550,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } for (int j = 0; j < UPDATE_LIST.length; j++) { @@ -550,6 +565,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -573,6 +589,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } for (int j = 0; j < UPDATE_LIST.length; j++) { @@ -587,6 +604,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -687,6 +705,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -705,6 +724,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -726,6 +746,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -774,6 +795,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } for (int j = 0; j < update.length; j++) { @@ -788,6 +810,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -807,6 +830,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } for (int j = 0; j < update.length; j++) { @@ -821,6 +845,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -844,6 +869,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } for (int j = 0; j < update.length; j++) { @@ -858,6 +884,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); diff --git a/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCountingReadOnlyIndexReaderFactory.java b/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCountingReadOnlyIndexReaderFactory.java index efd4283b6f..101bf27611 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCountingReadOnlyIndexReaderFactory.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCountingReadOnlyIndexReaderFactory.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -32,10 +33,10 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; -import org.apache.lucene.document.FieldSelector; -import org.apache.lucene.document.FieldSelectorResult; import org.apache.lucene.document.Field.Index; import org.apache.lucene.document.Field.Store; +import org.apache.lucene.document.FieldSelector; +import org.apache.lucene.document.FieldSelectorResult; import org.apache.lucene.index.FilterIndexReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.Term; @@ -47,7 +48,7 @@ public class ReferenceCountingReadOnlyIndexReaderFactory { private static Log s_logger = LogFactory.getLog(ReferenceCountingReadOnlyIndexReaderFactory.class); - private static HashMap log = new HashMap(); + private static WeakHashMap log = new WeakHashMap(); public static IndexReader createReader(String id, IndexReader indexReader, boolean enableCaching, LuceneConfig config) { @@ -59,7 +60,7 @@ public class ReferenceCountingReadOnlyIndexReaderFactory s_logger.debug("Replacing ref counting reader for " + id); } s_logger.debug("Created ref counting reader for " + id + " " + rc.toString()); - log.put(id, rc); + log.put(new String(id), rc); // Copy the key because the RCROIR references the ID } return rc; } diff --git a/source/java/org/alfresco/repo/search/impl/parsers/CMISTest.java b/source/java/org/alfresco/repo/search/impl/parsers/CMISTest.java index 2486c97898..2af4fff2df 100644 --- a/source/java/org/alfresco/repo/search/impl/parsers/CMISTest.java +++ b/source/java/org/alfresco/repo/search/impl/parsers/CMISTest.java @@ -25,7 +25,6 @@ import junit.framework.TestCase; import org.alfresco.repo.node.BaseNodeServiceTest; import org.antlr.gunit.GrammarInfo; -import org.antlr.gunit.gUnitExecutor; import org.antlr.gunit.gUnitLexer; import org.antlr.gunit.gUnitParser; import org.antlr.runtime.ANTLRInputStream; diff --git a/source/java/org/alfresco/repo/search/impl/parsers/CMIS_FTSTest.java b/source/java/org/alfresco/repo/search/impl/parsers/CMIS_FTSTest.java index bf128d13f1..3e91d38690 100644 --- a/source/java/org/alfresco/repo/search/impl/parsers/CMIS_FTSTest.java +++ b/source/java/org/alfresco/repo/search/impl/parsers/CMIS_FTSTest.java @@ -26,7 +26,6 @@ import junit.framework.TestCase; import org.alfresco.repo.node.BaseNodeServiceTest; import org.antlr.gunit.GrammarInfo; -import org.antlr.gunit.gUnitExecutor; import org.antlr.gunit.gUnitLexer; import org.antlr.gunit.gUnitParser; import org.antlr.runtime.ANTLRInputStream; diff --git a/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryAfterInvocationProvider.java b/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryAfterInvocationProvider.java index f37b2f6d55..6bb1ba9f33 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryAfterInvocationProvider.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryAfterInvocationProvider.java @@ -733,8 +733,16 @@ public class ACLEntryAfterInvocationProvider implements AfterInvocationProvider, for (int i = 0; i < returnedObject.length(); i++) { long currentTimeMillis = System.currentTimeMillis(); - if (i >= maxChecks || (currentTimeMillis - startTimeMillis) > maxCheckTime) + if (i >= maxChecks) { + log.warn("maxChecks exceeded (" + maxChecks + ")", new Exception("Back Trace")); + filteringResultSet.setResultSetMetaData(new SimpleResultSetMetaData(LimitBy.NUMBER_OF_PERMISSION_EVALUATIONS, PermissionEvaluationMode.EAGER, returnedObject + .getResultSetMetaData().getSearchParameters())); + break; + } + else if ((currentTimeMillis - startTimeMillis) > maxCheckTime) + { + log.warn("maxCheckTime exceeded (" + (currentTimeMillis - startTimeMillis) + " milliseconds)", new Exception("Back Trace")); filteringResultSet.setResultSetMetaData(new SimpleResultSetMetaData(LimitBy.NUMBER_OF_PERMISSION_EVALUATIONS, PermissionEvaluationMode.EAGER, returnedObject .getResultSetMetaData().getSearchParameters())); break; diff --git a/source/java/org/alfresco/repo/site/SiteAspect.java b/source/java/org/alfresco/repo/site/SiteAspect.java index 6f3a25e62b..2fec100ae9 100644 --- a/source/java/org/alfresco/repo/site/SiteAspect.java +++ b/source/java/org/alfresco/repo/site/SiteAspect.java @@ -23,9 +23,11 @@ import org.alfresco.repo.node.NodeServicePolicies.OnMoveNodePolicy; import org.alfresco.repo.policy.Behaviour; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; /** * Site aspect behaviour bean. @@ -38,9 +40,20 @@ import org.alfresco.service.cmr.repository.NodeService; public class SiteAspect implements NodeServicePolicies.OnMoveNodePolicy { /** Services */ + private DictionaryService dictionaryService; private PolicyComponent policyComponent; private NodeService nodeService; + /** + * Set the dictionary service + * + * @param dictionaryService dictionary service + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + /** * Set the policy component * @@ -88,7 +101,8 @@ public class SiteAspect implements NodeServicePolicies.OnMoveNodePolicy // Deny renames if (oldParent.equals(newParent)) { - if (nodeService.getType((oldChildAssocRef.getChildRef())).equals(SiteModel.TYPE_SITE)) + QName type = nodeService.getType((oldChildAssocRef.getChildRef())); + if (dictionaryService.isSubClass(type, SiteModel.TYPE_SITE)) { throw new SiteServiceException("Sites can not be renamed."); } diff --git a/source/java/org/alfresco/repo/site/SiteServiceImpl.java b/source/java/org/alfresco/repo/site/SiteServiceImpl.java index f8f32aa080..01827e5e6b 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceImpl.java +++ b/source/java/org/alfresco/repo/site/SiteServiceImpl.java @@ -413,7 +413,8 @@ public class SiteServiceImpl implements SiteService, SiteModel // Create the site's groups String siteGroup = authorityService .createAuthority(AuthorityType.GROUP, getSiteGroup(shortName, false), shortName, shareZones); - Set permissions = permissionService.getSettablePermissions(SiteModel.TYPE_SITE); + QName siteType = nodeService.getType(siteNodeRef); + Set permissions = permissionService.getSettablePermissions(siteType); for (String permission : permissions) { // Create a group for the permission @@ -431,7 +432,8 @@ public class SiteServiceImpl implements SiteService, SiteModel // - give all authorities read permission on permissions so // memberships can be calculated // - add the current user to the site manager group - if (SiteVisibility.PUBLIC.equals(visibility) == true) + if (SiteVisibility.PUBLIC.equals(visibility) == true && + permissions.contains(SITE_CONSUMER)) { // From Alfresco 3.4 the 'site public' group is configurable. Out of the box it is // GROUP_EVERYONE so unconfigured behaviour is unchanged. But from 3.4 admins @@ -449,7 +451,8 @@ public class SiteServiceImpl implements SiteService, SiteModel permissionService.setPermission(siteNodeRef, sitePublicGroup, SITE_CONSUMER, true); } - else if (SiteVisibility.MODERATED.equals(visibility) == true) + else if (SiteVisibility.MODERATED.equals(visibility) == true && + permissions.contains(SITE_CONSUMER)) { // for moderated site EVERYONE has consumer access but site components do not. permissionService.setPermission(siteNodeRef, PermissionService.ALL_AUTHORITIES, SITE_CONSUMER, true); @@ -615,7 +618,11 @@ public class SiteServiceImpl implements SiteService, SiteModel } }, AuthenticationUtil.getSystemUserName()); - siteHomeRefs.put(tenantDomain, siteHomeRef); + // There may be domains with no sites (e.g. JSF-only clients). + if (siteHomeRef != null) + { + siteHomeRefs.put(tenantDomain, siteHomeRef); + } } return siteHomeRef; } @@ -1085,11 +1092,12 @@ public class SiteServiceImpl implements SiteService, SiteModel public void deleteSite(final String shortName) { logger.debug("delete site :" + shortName); - NodeRef siteNodeRef = getSiteNodeRef(shortName); + final NodeRef siteNodeRef = getSiteNodeRef(shortName); if (siteNodeRef == null) { throw new SiteServiceException(MSG_CAN_NOT_DELETE, new Object[]{shortName}); } + final QName siteType = nodeService.getType(siteNodeRef); // Delete the cached reference String cacheKey = this.tenantAdminService.getCurrentUserDomain() + '_' + shortName; @@ -1122,7 +1130,7 @@ public class SiteServiceImpl implements SiteService, SiteModel authorityService.deleteAuthority(getSiteGroup(shortName, true), false); // Iterate over the role related groups and delete then - Set permissions = permissionService.getSettablePermissions(SiteModel.TYPE_SITE); + Set permissions = permissionService.getSettablePermissions(siteType); for (String permission : permissions) { String siteRoleGroup = getSiteRoleGroup(shortName, permission, true); @@ -1190,8 +1198,9 @@ public class SiteServiceImpl implements SiteService, SiteModel } Map members = new HashMap(32); - - Set permissions = this.permissionService.getSettablePermissions(SiteModel.TYPE_SITE); + + QName siteType = nodeService.getType(siteNodeRef); + Set permissions = this.permissionService.getSettablePermissions(siteType); for (String permission : permissions) { if (roleFilter == null || roleFilter.length() == 0 || roleFilter.equals(permission)) @@ -1373,8 +1382,15 @@ public class SiteServiceImpl implements SiteService, SiteModel */ private List getPermissionGroups(String siteShortName, String authorityName) { + NodeRef siteNodeRef = getSiteNodeRef(siteShortName); + if (siteNodeRef == null) + { + throw new SiteServiceException(MSG_SITE_NO_EXIST, new Object[] { siteShortName }); + } + List fullResult = new ArrayList(5); - Set roles = this.permissionService.getSettablePermissions(SiteModel.TYPE_SITE); + QName siteType = nodeService.getType(siteNodeRef); + Set roles = this.permissionService.getSettablePermissions(siteType); // First use the authority's cached recursive group memberships to answer the question quickly Set authorityGroups = this.authorityService.getContainingAuthorities(AuthorityType.GROUP, @@ -1416,8 +1432,30 @@ public class SiteServiceImpl implements SiteService, SiteModel */ public List getSiteRoles() { - Set permissions = permissionService - .getSettablePermissions(SiteModel.TYPE_SITE); + return getSiteRoles(SiteModel.TYPE_SITE); + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#getSiteRoles(String) + */ + public List getSiteRoles(String shortName) + { + NodeRef siteNodeRef = getSiteNodeRef(shortName); + if (siteNodeRef == null) + { + throw new SiteServiceException(MSG_SITE_NO_EXIST, new Object[] { shortName }); + } + QName siteType = nodeService.getType(siteNodeRef); + return getSiteRoles(siteType); + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#getSiteRoles() + * @see org.alfresco.service.cmr.site.SiteService#getSiteRoles(String) + */ + public List getSiteRoles(QName type) + { + Set permissions = permissionService.getSettablePermissions(type); return new ArrayList(permissions); } @@ -1709,7 +1747,14 @@ public class SiteServiceImpl implements SiteService, SiteModel */ private void setModeratedPermissions(String shortName, NodeRef containerNodeRef) { - Set permissions = permissionService.getSettablePermissions(SiteModel.TYPE_SITE); + NodeRef siteNodeRef = getSiteNodeRef(shortName); + if (siteNodeRef == null) + { + throw new SiteServiceException(MSG_SITE_NO_EXIST, new Object[] { shortName }); + } + + QName siteType = nodeService.getType(siteNodeRef); + Set permissions = permissionService.getSettablePermissions(siteType); for (String permission : permissions) { String permissionGroup = getSiteRoleGroup(shortName, permission, true); diff --git a/source/java/org/alfresco/repo/site/SiteServiceImplTest.java b/source/java/org/alfresco/repo/site/SiteServiceImplTest.java index b9ffe2a2db..356f947704 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceImplTest.java +++ b/source/java/org/alfresco/repo/site/SiteServiceImplTest.java @@ -18,8 +18,12 @@ */ package org.alfresco.repo.site; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + import java.io.Serializable; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -27,12 +31,18 @@ import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.model.ForumModel; +import org.alfresco.repo.dictionary.DictionaryDAO; +import org.alfresco.repo.dictionary.M2Model; +import org.alfresco.repo.dictionary.M2Property; +import org.alfresco.repo.dictionary.M2Type; import org.alfresco.repo.admin.SysAdminParams; import org.alfresco.repo.jscript.ClasspathScriptLocation; import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory; import org.alfresco.repo.node.archive.NodeArchiveService; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.TypeDefinition; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.repository.ContentWriter; @@ -50,6 +60,7 @@ import org.alfresco.service.cmr.site.SiteInfo; import org.alfresco.service.cmr.site.SiteService; import org.alfresco.service.cmr.site.SiteVisibility; import org.alfresco.service.cmr.tagging.TaggingService; +import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.BaseAlfrescoSpringTest; import org.alfresco.util.GUID; @@ -79,6 +90,8 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest private CopyService copyService; private ScriptService scriptService; private NodeService nodeService; + private NamespaceService namespaceService; + private DictionaryService dictionaryService; private AuthenticationComponent authenticationComponent; private TaggingService taggingService; private PersonService personService; @@ -118,6 +131,8 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest this.fileFolderService = (FileFolderService)this.applicationContext.getBean("FileFolderService"); this.nodeArchiveService = (NodeArchiveService)this.applicationContext.getBean("nodeArchiveService"); this.permissionService = (PermissionService)this.applicationContext.getBean("PermissionService"); + this.dictionaryService = (DictionaryService)this.applicationContext.getBean("DictionaryService"); + this.namespaceService = (NamespaceService)this.applicationContext.getBean("namespaceService"); this.siteService = (SiteService)this.applicationContext.getBean("SiteService"); // Big 'S' this.siteServiceImpl = (SiteServiceImpl) applicationContext.getBean("siteService"); // Small 's' this.sysAdminParams = (SysAdminParams)this.applicationContext.getBean("sysAdminParams"); @@ -984,11 +999,15 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest List roles = this.siteService.getSiteRoles(); assertNotNull(roles); assertFalse(roles.isEmpty()); + + // By default there are just the 4 roles + assertEquals(4, roles.size()); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONSUMER)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONTRIBUTOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_COLLABORATOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_MANAGER)); -// for (String role : roles) -// { -// System.out.println("Role: " + role); -// } + // For custom roles, see testCustomSiteType() } public void testCustomSiteProperties() @@ -1016,7 +1035,137 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest assertFalse(siteInfo.getCustomProperties().isEmpty()); assertEquals(1, siteInfo.getCustomProperties().size()); assertEquals("information", siteInfo.getCustomProperties().get(additionalInformationQName)); + } + + /** + * Creates a site with a custom type, and ensures that + * it behaves correctly. + */ + public void testCustomSiteType() + { + final String CS_URI = "http://example.com/site"; + final String CS_PFX = "cs"; + // Setup our custom site type + DictionaryDAO dictionaryDAO = (DictionaryDAO)this.applicationContext.getBean("dictionaryDAO"); + M2Model model = M2Model.createModel("cm:CustomSiteModel"); + model.createNamespace(CS_URI, CS_PFX); + + // Import the usual suspects too + model.createImport( + NamespaceService.CONTENT_MODEL_1_0_URI, + NamespaceService.CONTENT_MODEL_PREFIX + ); + model.createImport( + NamespaceService.DICTIONARY_MODEL_1_0_URI, + NamespaceService.DICTIONARY_MODEL_PREFIX + ); + model.createImport( + SiteModel.SITE_MODEL_URL, + SiteModel.SITE_MODEL_PREFIX + ); + + // Custom type + M2Type customType = model.createType("cs:customSite"); + customType.setTitle("customSite"); + customType.setParentName( + SiteModel.SITE_MODEL_PREFIX + ":" + + SiteModel.TYPE_SITE.getLocalName() + ); + + M2Property customProp = customType.createProperty("cs:customSiteProp"); + customProp.setTitle("customSiteProp"); + customProp.setType("d:text"); + dictionaryDAO.putModel(model); + + // Get our custom type, to check it's in there properly + final QName customTypeQ = QName.createQName("cs", "customSite", namespaceService); + TypeDefinition td = dictionaryService.getType(customTypeQ); + assertNotNull(td); + + // Create a site + SiteInfo site = siteService.createSite( + "custom", "custom", "Custom", "Custom", + SiteVisibility.PUBLIC + ); + + // Check the roles on it + List roles = siteService.getSiteRoles(); + assertEquals(4, roles.size()); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONSUMER)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONTRIBUTOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_COLLABORATOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_MANAGER)); + + roles = siteService.getSiteRoles(site.getShortName()); + assertEquals(4, roles.size()); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONSUMER)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONTRIBUTOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_COLLABORATOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_MANAGER)); + + + // Swap the type + nodeService.setType(site.getNodeRef(), customTypeQ); + + // Check again + roles = siteService.getSiteRoles(); + assertEquals(4, roles.size()); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONSUMER)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONTRIBUTOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_COLLABORATOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_MANAGER)); + + roles = siteService.getSiteRoles(site.getShortName()); + assertEquals(4, roles.size()); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONSUMER)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONTRIBUTOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_COLLABORATOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_MANAGER)); + + + // Alter the permissions for the custom site + PermissionService testPermissionService = spy( + (PermissionService)this.applicationContext.getBean("permissionServiceImpl") + ); + Set customPerms = new HashSet(); + customPerms.add(SiteServiceImpl.SITE_MANAGER); + customPerms.add("CUSTOM"); + when(testPermissionService.getSettablePermissions(customTypeQ)). + thenReturn(customPerms); + + // Check it changed for the custom site, but not normal + SiteServiceImpl siteServiceImpl = (SiteServiceImpl) + this.applicationContext.getBean("siteService"); + siteServiceImpl.setPermissionService(testPermissionService); + roles = siteService.getSiteRoles(); + + assertEquals(4, roles.size()); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONSUMER)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONTRIBUTOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_COLLABORATOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_MANAGER)); + + roles = siteService.getSiteRoles(site.getShortName()); + assertEquals(2, roles.size()); + assertEquals(true, roles.contains("CUSTOM")); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_MANAGER)); + + // Put the permissions back + siteServiceImpl.setPermissionService(permissionService); + roles = siteService.getSiteRoles(); + assertEquals(4, roles.size()); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONSUMER)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONTRIBUTOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_COLLABORATOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_MANAGER)); + + roles = siteService.getSiteRoles(site.getShortName()); + assertEquals(4, roles.size()); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONSUMER)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONTRIBUTOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_COLLABORATOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_MANAGER)); } public void testGroupMembership() diff --git a/source/java/org/alfresco/repo/site/script/ScriptSiteService.java b/source/java/org/alfresco/repo/site/script/ScriptSiteService.java index 445c48d961..4f30547b0c 100644 --- a/source/java/org/alfresco/repo/site/script/ScriptSiteService.java +++ b/source/java/org/alfresco/repo/site/script/ScriptSiteService.java @@ -217,11 +217,9 @@ public class ScriptSiteService extends BaseScopableProcessorExtension } /** - * This method cleans up the permissions on the specified node and all its primary children. * It removes permissions which pertain to sites other than the node's current site. * * @param targetNode the root node which is to have its permissions cleaned. - * @since 3.4.2 * @see SiteService#cleanSitePermissions(NodeRef, SiteInfo) */ public void cleanSitePermissions(NodeRef targetNode) @@ -233,7 +231,6 @@ public class ScriptSiteService extends BaseScopableProcessorExtension * This method cleans up the permissions on the specified node and all its primary children. * It removes permissions which pertain to sites other than the node's current site. * - * @param targetNode the root node which is to have its permissions cleaned. * @since 3.4.2 * @see SiteService#cleanSitePermissions(NodeRef, SiteInfo) */ @@ -241,4 +238,16 @@ public class ScriptSiteService extends BaseScopableProcessorExtension { this.cleanSitePermissions(targetNode.getNodeRef()); } + + /** + * Returns an array of all the roles that can be assigned to a member of a + * specific site. + * + * @return String[] roles available to assign to a member of a site + */ + public String[] listSiteRoles(String shortName) + { + List roles = this.siteService.getSiteRoles(shortName); + return (String[])roles.toArray(new String[roles.size()]); + } } diff --git a/source/java/org/alfresco/repo/site/script/Site.java b/source/java/org/alfresco/repo/site/script/Site.java index e708422319..73fd6be9a7 100644 --- a/source/java/org/alfresco/repo/site/script/Site.java +++ b/source/java/org/alfresco/repo/site/script/Site.java @@ -255,7 +255,9 @@ public class Site implements Serializable { if (this.siteRoleGroups == null) { - List roles = this.siteService.getSiteRoles(); + List roles = this.siteService.getSiteRoles( + this.siteInfo.getShortName() + ); this.siteRoleGroups = new ScriptableHashMap(); for (String role : roles) { diff --git a/source/java/org/alfresco/repo/tagging/TaggingServiceImplTest.java b/source/java/org/alfresco/repo/tagging/TaggingServiceImplTest.java index b339f0c3cc..5c1c641116 100644 --- a/source/java/org/alfresco/repo/tagging/TaggingServiceImplTest.java +++ b/source/java/org/alfresco/repo/tagging/TaggingServiceImplTest.java @@ -43,6 +43,7 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransacti import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.action.ActionTrackingService; +import org.alfresco.service.cmr.action.ExecutionSummary; import org.alfresco.service.cmr.audit.AuditService; import org.alfresco.service.cmr.coci.CheckOutCheckInService; import org.alfresco.service.cmr.repository.CopyService; @@ -63,6 +64,8 @@ import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.GUID; import org.alfresco.util.PropertyMap; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.context.ConfigurableApplicationContext; /** @@ -76,6 +79,8 @@ public class TaggingServiceImplTest extends TestCase private static ConfigurableApplicationContext ctx = (ConfigurableApplicationContext)ApplicationContextHelper.getApplicationContext(); + private static final Log logger = LogFactory.getLog(TaggingServiceImplTest.class); + /** Services */ private TaggingService taggingService; private NodeService nodeService; @@ -181,6 +186,11 @@ public class TaggingServiceImplTest extends TestCase new JavaBehaviour(asyncOccurs, "onAsyncActionExecute", NotificationFrequency.EVERY_EVENT) ); + // We do want action tracking whenever the tag scope updater runs + UpdateTagScopesActionExecuter updateTagsAction = + (UpdateTagScopesActionExecuter)ctx.getBean("update-tagscope"); + updateTagsAction.setTrackStatus(true); + // Create the folders and documents to be tagged createTestDocumentsAndFolders(); } @@ -1710,6 +1720,12 @@ public class TaggingServiceImplTest extends TestCase assertTrue("Not found in " + pendingScopes, pendingScopes.contains(subFolder)); + // Ensure that we've still got the lock, eg in case + // of the async execution taking a while to proceed + updateTagsAction.updateTagScopeLock(folder, testData.lockF); + updateTagsAction.updateTagScopeLock(subFolder, testData.lockSF); + + // Have the Quartz bean fire now // It won't be able to do anything, as the locks are taken UpdateTagScopesQuartzJob job = new UpdateTagScopesQuartzJob(); @@ -1775,7 +1791,7 @@ public class TaggingServiceImplTest extends TestCase * Test that when multiple threads do tag updates, the right thing still * happens */ - public void DISABLEDtestMultiThreaded() throws Exception + public void testMultiThreaded() throws Exception { transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { @@ -1791,6 +1807,7 @@ public class TaggingServiceImplTest extends TestCase // Reset the action count asyncOccurs.wantedActionsCount = 0; + // Prepare a bunch of threads to do tagging final List threads = new ArrayList(); final String[] tags = new String[] { TAG_1, TAG_2, TAG_3, TAG_4, TAG_5, @@ -1813,7 +1830,7 @@ public class TaggingServiceImplTest extends TestCase catch (InterruptedException e) { } - System.out.println(Thread.currentThread() + " - About to start tagging for " + tag); + logger.debug(Thread.currentThread() + " - About to start tagging for " + tag); // Do the updates AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName()); @@ -1825,12 +1842,12 @@ public class TaggingServiceImplTest extends TestCase taggingService.addTag(folder, tag); taggingService.addTag(subFolder, tag); taggingService.addTag(subDocument, tag); - System.out.println(Thread.currentThread() + " - Tagging for " + tag); + logger.debug(Thread.currentThread() + " - Tagging for " + tag); return null; } }, false, true ); - System.out.println(Thread.currentThread() + " - Done tagging for " + tag); + logger.debug(Thread.currentThread() + " - Done tagging for " + tag); // Wait briefly for thing to catch up, before we // declare ourselves to be done @@ -1844,7 +1861,7 @@ public class TaggingServiceImplTest extends TestCase } // Release the threads - System.out.println("Releasing tagging threads"); + logger.info("Releasing tagging threads"); for (Thread t : threads) { t.interrupt(); @@ -1856,7 +1873,7 @@ public class TaggingServiceImplTest extends TestCase { t.join(); } - System.out.println("All threads should have finished"); + logger.info("All threads should have finished"); // Have a brief pause, while we wait for their related // async actions to kick off @@ -1873,11 +1890,20 @@ public class TaggingServiceImplTest extends TestCase { if(asyncOccurs.wantedActionsCount < tags.length) { + if(i%50 == 0) + { + logger.info("Done " + asyncOccurs.wantedActionsCount + " of " + tags.length); + } Thread.sleep(100); continue; } if (actionTrackingService.getAllExecutingActions().size() > 0) { + if(i%50 == 0) + { + List actions = actionTrackingService.getAllExecutingActions(); + logger.info("Waiting on " + actions.size() + " actions: " + actions); + } Thread.sleep(100); continue; } diff --git a/source/java/org/alfresco/repo/tagging/UpdateTagScopesActionExecuter.java b/source/java/org/alfresco/repo/tagging/UpdateTagScopesActionExecuter.java index bc358ff26f..cdf165f28a 100644 --- a/source/java/org/alfresco/repo/tagging/UpdateTagScopesActionExecuter.java +++ b/source/java/org/alfresco/repo/tagging/UpdateTagScopesActionExecuter.java @@ -93,6 +93,9 @@ public class UpdateTagScopesActionExecuter extends ActionExecuterAbstractBase /** What's the largest number of updates we should claim for a tag scope in one transaction? */ private static final int tagUpdateBatchSize = 100; + + /** How long to lock a tag scope for */ + private static final int tagScopeLockTime = 2500; // For searching private static final String noderefPath = @@ -540,10 +543,14 @@ public class UpdateTagScopesActionExecuter extends ActionExecuterAbstractBase protected String lockTagScope(NodeRef tagScope) { String lock = jobLockService.getLock( - tagScopeToLockQName(tagScope), 2500, 0, 0 + tagScopeToLockQName(tagScope), tagScopeLockTime, 0, 0 ); return lock; } + protected void updateTagScopeLock(NodeRef tagScope, String lockToken) + { + jobLockService.refreshLock(lockToken, tagScopeToLockQName(tagScope), tagScopeLockTime); + } protected void unlockTagScope(NodeRef tagScope, String lockToken) { jobLockService.releaseLock(lockToken, tagScopeToLockQName(tagScope)); diff --git a/source/java/org/alfresco/repo/template/AVMTemplateNode.java b/source/java/org/alfresco/repo/template/AVMTemplateNode.java index 0d99e4b7ad..c7a1ecf1d0 100644 --- a/source/java/org/alfresco/repo/template/AVMTemplateNode.java +++ b/source/java/org/alfresco/repo/template/AVMTemplateNode.java @@ -35,6 +35,7 @@ import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.avm.AVMNodeDescriptor; import org.alfresco.service.cmr.avm.locking.AVMLockingService.LockState; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentReader; @@ -371,7 +372,13 @@ public class AVMTemplateNode extends BasePermissionsNode implements NamespacePre Map props = this.services.getAVMService().getNodeProperties(this.version, this.path); for (QName qname: props.keySet()) { - Serializable propValue = props.get(qname).getValue(DataTypeDefinition.ANY); + PropertyDefinition propertyDefinition = services.getDictionaryService().getProperty(qname); + QName currentPropertyType = DataTypeDefinition.ANY; + if (null != propertyDefinition) + { + currentPropertyType = propertyDefinition.getDataType().getName(); + } + Serializable propValue = props.get(qname).getValue(currentPropertyType); if (propValue instanceof NodeRef) { // NodeRef object properties are converted to new TemplateNode objects diff --git a/source/java/org/alfresco/repo/template/AVMTemplateNodeTest.java b/source/java/org/alfresco/repo/template/AVMTemplateNodeTest.java new file mode 100644 index 0000000000..97b7bf8777 --- /dev/null +++ b/source/java/org/alfresco/repo/template/AVMTemplateNodeTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.template; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import junit.framework.TestCase; + +import org.alfresco.repo.avm.AVMNodeConverter; +import org.alfresco.repo.domain.PropertyValue; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.avm.AVMService; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ApplicationContextHelper; +import org.springframework.context.ApplicationContext; + +/** + * @author Dmitry Velichkevich + */ +public class AVMTemplateNodeTest extends TestCase +{ + private static final String TEST_WCM_NAMESPACE = "http://www.alfresco.org/model/testwcmmodel/1.0"; + + private static final QName ASPECT_AUTHORED = QName.createQName(TEST_WCM_NAMESPACE, "authored"); + private static final QName PROP_AUTHORED_DATE = QName.createQName(TEST_WCM_NAMESPACE, "dateAuthored"); + + private static final ApplicationContext APPLICATION_CONTEXT = ApplicationContextHelper.getApplicationContext(new String[] { "classpath:alfresco/application-context.xml", + "classpath:test/alfresco/wcm-template-node-test-context.xml" }); + private static final ServiceRegistry SERVICE_REGISTRY = (ServiceRegistry) APPLICATION_CONTEXT.getBean(ServiceRegistry.SERVICE_REGISTRY); + + private AVMService avmService = SERVICE_REGISTRY.getAVMService(); + + @Override + protected void setUp() throws Exception + { + avmService.createStore("main"); + avmService.createDirectory("main:/", "root"); + avmService.createFile("main:/root", "testfile.txt"); + } + + @Override + protected void tearDown() throws Exception + { + avmService.purgeStore("main"); + } + + @SuppressWarnings("unchecked") + public void testDatePropertiesConversion() throws Exception + { + assertNotNull("Aspect 'twcm:authored' is not in the set of compiled dictionary models", SERVICE_REGISTRY.getDictionaryService().getAspect(ASPECT_AUTHORED)); + + List values = new LinkedList(); + for (int i = 0; i < 5; i++) + { + values.add(new Date()); + } + PropertyValue value = new PropertyValue(DataTypeDefinition.TEXT, (Serializable) values); + + avmService.addAspect("main:/root/testfile.txt", ASPECT_AUTHORED); + avmService.setNodeProperty("main:/root/testfile.txt", PROP_AUTHORED_DATE, value); + NodeRef nodeRef = AVMNodeConverter.ToNodeRef(-1, "main:/root/testfile.txt"); + AVMTemplateNode templateNode = new AVMTemplateNode(nodeRef, SERVICE_REGISTRY, null); + + Map properties = templateNode.getProperties(); + assertNotNull(properties); + assertFalse(properties.isEmpty()); + assertTrue(properties.containsKey(PROP_AUTHORED_DATE)); + + Collection authoredDates = (Collection) properties.get(PROP_AUTHORED_DATE); + assertNotNull(authoredDates); + assertFalse(authoredDates.isEmpty()); + + for (Serializable date : authoredDates) + { + assertFalse(("Unexpected data type of 'twcm:authored' property values: " + ((null != date) ? (date.getClass().getName()) : ("null"))), date instanceof String); + } + } +} diff --git a/source/java/org/alfresco/repo/transfer/TransferServiceImpl2.java b/source/java/org/alfresco/repo/transfer/TransferServiceImpl2.java index 7b469f9fe8..58785fa3f5 100644 --- a/source/java/org/alfresco/repo/transfer/TransferServiceImpl2.java +++ b/source/java/org/alfresco/repo/transfer/TransferServiceImpl2.java @@ -237,14 +237,7 @@ public class TransferServiceImpl2 implements TransferService2 // enableable aspect properties.put(TransferModel.PROP_ENABLED, Boolean.TRUE); - NodeRef home = getTransferHome(); - - /** - * Work out which group the transfer target is for, in this case the - * default group. - */ - NodeRef defaultGroup = nodeService.getChildByName(home, ContentModel.ASSOC_CONTAINS, - defaultTransferGroup); + NodeRef defaultGroup = getDefaultGroup(); /** * Go ahead and create the new node @@ -262,6 +255,14 @@ public class TransferServiceImpl2 implements TransferService2 return retVal; } + private NodeRef getDefaultGroup() + { + NodeRef home = getTransferHome(); + NodeRef defaultGroup = nodeService.getChildByName(home, ContentModel.ASSOC_CONTAINS, + defaultTransferGroup); + return defaultGroup; + } + /** * Get all transfer targets */ @@ -1184,18 +1185,28 @@ public class TransferServiceImpl2 implements TransferService2 { String query = transferSpaceQuery; - ResultSet result = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, - SearchService.LANGUAGE_XPATH, query); - - if(result.length() == 0) + ResultSet result = null; + try { - // No transfer home. - throw new TransferException(MSG_NO_HOME, new Object[]{query}); + result = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, query); + + if (result.length() == 0) + { + // No transfer home. + throw new TransferException(MSG_NO_HOME, new Object[] { query }); + } + if (result.getNodeRefs().size() != 0) + { + transferHome = result.getNodeRef(0); + transferHomeMap.put(tenantDomain, transferHome); + } } - if (result.getNodeRefs().size() != 0) + finally { - transferHome = result.getNodeRef(0); - transferHomeMap.put(tenantDomain, transferHome); + if (result != null) + { + result.close(); + } } } return transferHome; @@ -1225,16 +1236,8 @@ public class TransferServiceImpl2 implements TransferService2 */ private NodeRef lookupTransferTarget(String name) { - String query = "+TYPE:\"trx:transferTarget\" +@cm\\:name:\"" +name + "\""; - - ResultSet result = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, - SearchService.LANGUAGE_LUCENE, query); - - if(result.length() == 1) - { - return result.getNodeRef(0); - } - return null; + NodeRef defaultGroup = getDefaultGroup(); + return nodeService.getChildByName(defaultGroup, ContentModel.ASSOC_CONTAINS, name); } private void mapTransferTarget(NodeRef nodeRef, TransferTargetImpl def) diff --git a/source/java/org/alfresco/repo/transfer/TransferServiceImplTest.java b/source/java/org/alfresco/repo/transfer/TransferServiceImplTest.java index 8f941ea154..7eac2e4bea 100644 --- a/source/java/org/alfresco/repo/transfer/TransferServiceImplTest.java +++ b/source/java/org/alfresco/repo/transfer/TransferServiceImplTest.java @@ -342,6 +342,52 @@ public class TransferServiceImplTest extends BaseAlfrescoSpringTest } } + /** + * Test that if someone copies a transfer group using a client app then the getTransferTarget operations still succeed + * + * @throws Exception + */ + public void testALF6565() throws Exception + { + String nameA = GUID.generate(); + String nameB = GUID.generate(); + String title = "title"; + String description = "description"; + String endpointProtocol = "http"; + String endpointHost = "localhost"; + int endpointPort = 8080; + String endpointPath = "rhubarb"; + String username = "admin"; + char[] password = "password".toCharArray(); + + /** + * Now go ahead and create our first transfer target + */ + TransferTarget targetA = transferService.createAndSaveTransferTarget(nameA, title, description, endpointProtocol, endpointHost, endpointPort, endpointPath, username, password); + TransferTarget targetB = transferService.createAndSaveTransferTarget(nameB, title, description, endpointProtocol, endpointHost, endpointPort, endpointPath, username, password); + + NodeRef transferHome = transferServiceImpl.getTransferHome(); + NodeRef defaultGroup = nodeService.getChildByName(transferHome, ContentModel.ASSOC_CONTAINS, + transferServiceImpl.getDefaultTransferGroup()); + assertNotNull(defaultGroup); + copyService.copyAndRename(defaultGroup, transferHome, ContentModel.ASSOC_CONTAINS, QName.createQName("test"), true); + + Set targets = transferService.getTransferTargets(); + + int targetACount = 0; + int targetBCount = 0; + for (TransferTarget target : targets) + { + if (target.getName().equals(nameA)) ++targetACount; + if (target.getName().equals(nameB)) ++targetBCount; + } + assertEquals(2, targetACount); + assertEquals(2, targetBCount); + + assertEquals(targetA.getNodeRef(), transferService.getTransferTarget(nameA).getNodeRef()); + assertEquals(targetB.getNodeRef(), transferService.getTransferTarget(nameB).getNodeRef()); + } + /** * Test of Get All Transfer Targets By Group */ diff --git a/source/java/org/alfresco/repo/usage/ContentUsageImpl.java b/source/java/org/alfresco/repo/usage/ContentUsageImpl.java index 2d3aa85446..017e7b2fc7 100644 --- a/source/java/org/alfresco/repo/usage/ContentUsageImpl.java +++ b/source/java/org/alfresco/repo/usage/ContentUsageImpl.java @@ -502,7 +502,7 @@ public class ContentUsageImpl implements ContentUsageService, } } - private long getUserStoredUsage(NodeRef personNodeRef) + public long getUserStoredUsage(NodeRef personNodeRef) { Long currentUsage = null; if (personNodeRef != null) @@ -516,10 +516,10 @@ public class ContentUsageImpl implements ContentUsageService, public long getUserUsage(String userName) { ParameterCheck.mandatoryString("userName", userName); - return getUserUsage(getPerson(userName)); + return getUserUsage(getPerson(userName), false); } - public long getUserUsage(NodeRef personNodeRef) + public long getUserUsage(NodeRef personNodeRef, boolean removeDeltas) { long currentUsage = -1; @@ -530,8 +530,10 @@ public class ContentUsageImpl implements ContentUsageService, if (currentUsage != -1) { - // add any deltas - currentUsage = currentUsage + usageService.getTotalDeltaSize(personNodeRef); + long deltaSize = removeDeltas ? usageService.getAndRemoveTotalDeltaSize(personNodeRef) : + usageService.getTotalDeltaSize(personNodeRef); + // add any deltas to the currentUsage, removing them if required + currentUsage = currentUsage + deltaSize; if (currentUsage < 0) { diff --git a/source/java/org/alfresco/repo/usage/UsageServiceImpl.java b/source/java/org/alfresco/repo/usage/UsageServiceImpl.java index 17cd2ce141..16037855e2 100644 --- a/source/java/org/alfresco/repo/usage/UsageServiceImpl.java +++ b/source/java/org/alfresco/repo/usage/UsageServiceImpl.java @@ -53,7 +53,7 @@ public class UsageServiceImpl implements UsageService { return usageDAO.getTotalDeltaSize(usageNodeRef, true); } - + public Set getUsageDeltaNodes() { return usageDAO.getUsageDeltaNodes(); diff --git a/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java b/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java index 7a013387b0..90b0c67a22 100644 --- a/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java +++ b/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java @@ -556,23 +556,28 @@ public class UserUsageTrackingComponent extends AbstractLifecycleBean if (nodeType.equals(ContentModel.TYPE_PERSON)) { NodeRef personNodeRef = usageNodeRef; - // collapse the usage deltas - long currentUsage = contentUsageImpl.getUserUsage(personNodeRef); + String userName = (String)nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); + + long currentUsage = contentUsageImpl.getUserStoredUsage(personNodeRef); if (currentUsage != -1) { - usageService.deleteDeltas(personNodeRef); + // Collapse the usage deltas + // Calculate and remove deltas in one go to guard against deletion of + // deltas from another transaction that have not been included in the + // calculation + currentUsage = contentUsageImpl.getUserUsage(personNodeRef, true); contentUsageImpl.setUserStoredUsage(personNodeRef, currentUsage); if (logger.isTraceEnabled()) { - logger.trace("Collapsed usage: personNodeRef=" + personNodeRef + ", usage=" + currentUsage); + logger.trace("Collapsed usage: username=" + userName + ", usage=" + currentUsage); } } else { if (logger.isWarnEnabled()) { - logger.warn("Initial usage has not yet been calculated: personNodeRef=" + personNodeRef); + logger.warn("Initial usage for user has not yet been calculated: " + userName); } } } diff --git a/source/java/org/alfresco/repo/version/NodeServiceImplTest.java b/source/java/org/alfresco/repo/version/NodeServiceImplTest.java index aafcceac20..8bdf34d367 100644 --- a/source/java/org/alfresco/repo/version/NodeServiceImplTest.java +++ b/source/java/org/alfresco/repo/version/NodeServiceImplTest.java @@ -132,7 +132,7 @@ public class NodeServiceImplTest extends BaseVersionStoreTest //assertEquals(origProps.size(), versionedProperties.size()); // check version label - assertEquals("1.0", versionedProperties.get(ContentModel.PROP_VERSION_LABEL)); + assertEquals("first version label", "0.1", versionedProperties.get(ContentModel.PROP_VERSION_LABEL)); // TODO do futher versioning and check by changing values } @@ -622,8 +622,8 @@ public class NodeServiceImplTest extends BaseVersionStoreTest // Now French - String frProp = "C'est une propri\u00e9t\u00e9 en fran\u00e7ais"; // C'est une propriété en français - QName frQName = QName.createQName("NameSpace", "En Fran\u00e7ais"); // En Français + String frProp = "C'est une propri\u00e9t\u00e9 en fran\u00e7ais"; // C'est une propriété en français + QName frQName = QName.createQName("NameSpace", "En Fran\u00e7ais"); // En Français NodeRef frNode = nodeService.createNode( this.rootNodeRef, ContentModel.ASSOC_CONTAINS, frQName, ContentModel.TYPE_CONTENT @@ -645,8 +645,8 @@ public class NodeServiceImplTest extends BaseVersionStoreTest // Next Spanish - String esProp = "Esta es una propiedad en Espa\u00f1ol"; // Esta es una propiedad en Español - QName esQName = QName.createQName("NameSpace", "En Espa\u00f1ol"); // En Español + String esProp = "Esta es una propiedad en Espa\u00f1ol"; // Esta es una propiedad en Español + QName esQName = QName.createQName("NameSpace", "En Espa\u00f1ol"); // En Español NodeRef esNode = nodeService.createNode( this.rootNodeRef, ContentModel.ASSOC_CONTAINS, esQName, ContentModel.TYPE_CONTENT @@ -666,8 +666,8 @@ public class NodeServiceImplTest extends BaseVersionStoreTest // Finally Japanese - String jpProp = "\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u304f\u3060\u3055\u3044\u3002"; // をクリックしてください。 - QName jpQName = QName.createQName("NameSpace", "\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u304f"); // をクリックしてく + String jpProp = "\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u304f\u3060\u3055\u3044\u3002"; // をクリックã�—ã�¦ã��ã� ã�•ã�„。 + QName jpQName = QName.createQName("NameSpace", "\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u304f"); // をクリックã�—ã�¦ã�� NodeRef jpNode = nodeService.createNode( this.rootNodeRef, ContentModel.ASSOC_CONTAINS, jpQName, ContentModel.TYPE_CONTENT diff --git a/source/java/org/alfresco/repo/version/VersionServiceImplTest.java b/source/java/org/alfresco/repo/version/VersionServiceImplTest.java index 2df349ae2f..bddcc27a50 100644 --- a/source/java/org/alfresco/repo/version/VersionServiceImplTest.java +++ b/source/java/org/alfresco/repo/version/VersionServiceImplTest.java @@ -52,6 +52,7 @@ import org.alfresco.service.cmr.version.VersionHistory; import org.alfresco.service.cmr.version.VersionService; import org.alfresco.service.cmr.version.VersionServiceException; import org.alfresco.service.cmr.version.VersionType; +import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.GUID; @@ -93,6 +94,7 @@ public class VersionServiceImplTest extends BaseVersionStoreTest { super.onTearDownAfterTransaction(); versionableAspect.setExcludedOnUpdateProps(excludedOnUpdateProps); + versionableAspect.afterDictionaryInit(); } public void testSetup() @@ -585,13 +587,13 @@ public class VersionServiceImplTest extends BaseVersionStoreTest // Check that the version label is correct on the versionable node String versionLabel1 = (String)this.dbNodeService.getProperty(versionableNode, ContentModel.PROP_VERSION_LABEL); - assertEquals("1.1", versionLabel1); + assertEquals("first version label", "0.2", versionLabel1); assertEquals(version1.getVersionLabel(), versionLabel1); // Check the version history List expectedVersions = new ArrayList(2); - expectedVersions.add(version1); - expectedVersions.add(version0); + expectedVersions.add(version1); // 0.1 + expectedVersions.add(version0); // 0.2 versionHistory = this.versionService.getVersionHistory(versionableNode); assertEquals(2, versionHistory.getAllVersions().size()); CheckVersionHistory(versionHistory, expectedVersions); @@ -602,12 +604,12 @@ public class VersionServiceImplTest extends BaseVersionStoreTest assertEquals(currentVersion.getFrozenStateNodeRef(), version1.getFrozenStateNodeRef()); // Create a couple more versions - Version version2 = createVersion(versionableNode); - Version version3 = createVersion(versionableNode); + Version version2 = createVersion(versionableNode); // 0.3 + Version version3 = createVersion(versionableNode); //0.4 // Check that the version label is correct on the versionable node String versionLabel3 = (String)this.dbNodeService.getProperty(versionableNode, ContentModel.PROP_VERSION_LABEL); - assertEquals("1.3", versionLabel3); + assertEquals("0.4", versionLabel3); assertEquals(version3.getVersionLabel(), versionLabel3); // Check the version history @@ -1069,8 +1071,10 @@ public class VersionServiceImplTest extends BaseVersionStoreTest }); List excludedOnUpdateProps = new ArrayList(1); - excludedOnUpdateProps.add(ContentModel.PROP_AUTHOR.toPrefixString()); + NamespaceService namespaceService = (NamespaceService) applicationContext.getBean("namespaceService"); + excludedOnUpdateProps.add(ContentModel.PROP_AUTHOR.toPrefixString(namespaceService)); versionableAspect.setExcludedOnUpdateProps(excludedOnUpdateProps); + versionableAspect.afterDictionaryInit(); // test auto-version props on - with an excluded prop change diff --git a/source/java/org/alfresco/repo/version/VersionableAspect.java b/source/java/org/alfresco/repo/version/VersionableAspect.java index bdcb5ea092..515dc0aa45 100644 --- a/source/java/org/alfresco/repo/version/VersionableAspect.java +++ b/source/java/org/alfresco/repo/version/VersionableAspect.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -21,8 +21,10 @@ package org.alfresco.repo.version; import java.io.Serializable; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.ContentServicePolicies; @@ -30,6 +32,8 @@ import org.alfresco.repo.copy.CopyBehaviourCallback; import org.alfresco.repo.copy.CopyDetails; import org.alfresco.repo.copy.CopyServicePolicies; import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; +import org.alfresco.repo.dictionary.DictionaryDAO; +import org.alfresco.repo.dictionary.DictionaryListener; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.Behaviour; import org.alfresco.repo.policy.JavaBehaviour; @@ -41,6 +45,7 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.version.Version; import org.alfresco.service.cmr.version.VersionService; import org.alfresco.service.cmr.version.VersionType; +import org.alfresco.service.namespace.NamespacePrefixResolver; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.EqualsHelper; @@ -57,7 +62,8 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate NodeServicePolicies.OnDeleteNodePolicy, NodeServicePolicies.OnUpdatePropertiesPolicy, VersionServicePolicies.AfterCreateVersionPolicy, - CopyServicePolicies.OnCopyNodePolicy + CopyServicePolicies.OnCopyNodePolicy, + DictionaryListener { /** The i18n'ized messages */ private static final String MSG_INITIAL_VERSION = "create_version.initial_version"; @@ -75,6 +81,12 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate /** The Version service */ private VersionService versionService; + + /** The dictionary DAO. */ + private DictionaryDAO dictionaryDAO; + + /** The Namespace Prefix Resolver. */ + private NamespacePrefixResolver namespacePrefixResolver; /** Behaviours */ JavaBehaviour onUpdatePropertiesBehaviour; @@ -86,6 +98,8 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate */ private List excludedOnUpdateProps = Collections.emptyList(); + private Set excludedOnUpdatePropQNames = Collections.emptySet(); + /** * Set the policy component * @@ -115,8 +129,30 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate { this.nodeService = nodeService; } + + /** + * Sets the dictionary DAO. + * + * @param dictionaryDAO + * the dictionary DAO + */ + public void setDictionaryDAO(DictionaryDAO dictionaryDAO) + { + this.dictionaryDAO = dictionaryDAO; + } /** + * Sets the namespace prefix resolver. + * + * @param namespacePrefixResolver + * the namespace prefix resolver + */ + public void setNamespacePrefixResolver(NamespacePrefixResolver namespacePrefixResolver) + { + this.namespacePrefixResolver = namespacePrefixResolver; + } + + /** * @return Returns the current list of properties that do not trigger versioning */ public List getExcludedOnUpdateProps() @@ -172,6 +208,8 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), ContentModel.ASPECT_VERSIONABLE, new JavaBehaviour(this, "getCopyCallback")); + + this.dictionaryDAO.register(this); } /** @@ -364,27 +402,16 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate if ((autoVersion == true) && (autoVersionProps == true)) { // Check for explicitly excluded props - if one or more excluded props changes then do not auto-version on this event (even if other props changed) - if (excludedOnUpdateProps.size() > 0) + if (excludedOnUpdatePropQNames.size() > 0) { - Map propNames = new HashMap(after.size()); - for (QName afterProp : after.keySet()) - { - if (excludedOnUpdateProps.contains(afterProp.getPrefixString())) - { - propNames.put(afterProp.getPrefixString(), afterProp); - } - } - for (QName beforeProp : before.keySet()) - { - if (excludedOnUpdateProps.contains(beforeProp.getPrefixString())) - { - propNames.put(beforeProp.getPrefixString(), beforeProp); - } - } - + Set propNames = new HashSet(after.size() * 2); + propNames.addAll(after.keySet()); + propNames.addAll(before.keySet()); + propNames.retainAll(excludedOnUpdatePropQNames); + if (propNames.size() > 0) { - for (QName prop : propNames.values()) + for (QName prop : propNames) { Serializable beforeValue = before.get(prop); Serializable afterValue = after.get(prop); @@ -447,4 +474,43 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate } versionedNodeRefs.put(versionableNode, versionableNode); } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.dictionary.DictionaryListener#onDictionaryInit() + */ + @Override + public void onDictionaryInit() + { + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.dictionary.DictionaryListener#afterDictionaryInit() + */ + @Override + public void afterDictionaryInit() + { + this.excludedOnUpdatePropQNames = new HashSet(this.excludedOnUpdateProps.size() * 2); + for (String prefixString : this.excludedOnUpdateProps) + { + try + { + this.excludedOnUpdatePropQNames.add(QName.createQName(prefixString, this.namespacePrefixResolver)); + } + catch (Exception e) + { + // An unregistered prefix. Ignore and continue + } + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.dictionary.DictionaryListener#afterDictionaryDestroy() + */ + @Override + public void afterDictionaryDestroy() + { + } } diff --git a/source/java/org/alfresco/repo/version/common/versionlabel/SerialVersionLabelPolicy.java b/source/java/org/alfresco/repo/version/common/versionlabel/SerialVersionLabelPolicy.java index beceeaeeb4..1b2dd26ebc 100644 --- a/source/java/org/alfresco/repo/version/common/versionlabel/SerialVersionLabelPolicy.java +++ b/source/java/org/alfresco/repo/version/common/versionlabel/SerialVersionLabelPolicy.java @@ -69,30 +69,32 @@ public class SerialVersionLabelPolicy implements CalculateVersionLabelPolicy { SerialVersionLabel serialVersionNumber = null; + VersionType versionType = null; + if (versionProperties != null) + { + versionType = (VersionType)versionProperties.get(VersionModel.PROP_VERSION_TYPE); + } + if (preceedingVersion != null) { + // There is a preceeding version serialVersionNumber = new SerialVersionLabel(preceedingVersion.getVersionLabel()); - - VersionType versionType = null; - if (versionProperties != null) - { - versionType = (VersionType)versionProperties.get(VersionModel.PROP_VERSION_TYPE); - } - - if (VersionType.MAJOR.equals(versionType) == true) - { - serialVersionNumber.majorIncrement(); - } - else - { - serialVersionNumber.minorIncrement(); - } } else { + // This is the first version serialVersionNumber = new SerialVersionLabel(null); } + if (VersionType.MAJOR.equals(versionType) == true) + { + serialVersionNumber.majorIncrement(); + } + else + { + serialVersionNumber.minorIncrement(); + } + return serialVersionNumber.toString(); } @@ -133,7 +135,7 @@ public class SerialVersionLabelPolicy implements CalculateVersionLabelPolicy } else { - majorRevisionNumber = 1; + majorRevisionNumber = 0; minorRevisionNumber = 0; } } diff --git a/source/java/org/alfresco/repo/version/common/versionlabel/SerialVersionLabelPolicyTest.java b/source/java/org/alfresco/repo/version/common/versionlabel/SerialVersionLabelPolicyTest.java index 642e5be4ea..60f586a66e 100644 --- a/source/java/org/alfresco/repo/version/common/versionlabel/SerialVersionLabelPolicyTest.java +++ b/source/java/org/alfresco/repo/version/common/versionlabel/SerialVersionLabelPolicyTest.java @@ -55,7 +55,7 @@ public class SerialVersionLabelPolicyTest extends TestCase null, 0, versionProp1); - assertEquals("1.0", initialVersion); + assertEquals("Minor initial version not 0.1", "0.1", initialVersion); HashMap versionProp2 = new HashMap(); versionProp2.put(VersionModel.PROP_VERSION_LABEL, "1.0"); @@ -66,7 +66,7 @@ public class SerialVersionLabelPolicyTest extends TestCase version1, 1, versionProp1); - assertEquals("1.1", verisonLabel1); + assertEquals("Minor update from 1.0 not correct", "1.1", verisonLabel1); HashMap versionProp3 = new HashMap(); versionProp3.put(VersionModel.PROP_VERSION_LABEL, "1.1"); @@ -80,7 +80,7 @@ public class SerialVersionLabelPolicyTest extends TestCase version2, 1, versionProp4); - assertEquals("2.0", verisonLabel2); + assertEquals("major version update not correct", "2.0", verisonLabel2); } } diff --git a/source/java/org/alfresco/repo/workflow/AbstractWorkflowServiceIntegrationTest.java b/source/java/org/alfresco/repo/workflow/AbstractWorkflowServiceIntegrationTest.java index a8c3b0b40f..e2812c8e88 100644 --- a/source/java/org/alfresco/repo/workflow/AbstractWorkflowServiceIntegrationTest.java +++ b/source/java/org/alfresco/repo/workflow/AbstractWorkflowServiceIntegrationTest.java @@ -22,6 +22,7 @@ package org.alfresco.repo.workflow; import java.io.InputStream; import java.io.Serializable; import java.util.Arrays; +import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -381,7 +382,12 @@ public abstract class AbstractWorkflowServiceIntegrationTest extends BaseSpringT assertEquals(1, tasks.size()); WorkflowTask currentTask = tasks.get(0); assertEquals(currentTask.getState(), WorkflowTaskState.IN_PROGRESS); - assertNull(currentTask.getProperties().get(ContentModel.PROP_OWNER)); + Map taskProperties = currentTask.getProperties(); + assertNull(taskProperties.get(ContentModel.PROP_OWNER)); + + Serializable pooledActors = taskProperties.get(WorkflowModel.ASSOC_POOLED_ACTORS); + assertNotNull(pooledActors); + assertTrue(((Collection)pooledActors).contains(group)); // ensure the task is not reassignable by any user assertFalse(workflowService.isTaskReassignable(currentTask, USER1)); @@ -419,6 +425,74 @@ public abstract class AbstractWorkflowServiceIntegrationTest extends BaseSpringT assertFalse(workflowService.isTaskClaimable(currentTask, USER2)); assertFalse(workflowService.isTaskEditable(currentTask, USER2)); + // Release the task + properties.clear(); + properties.put(ContentModel.PROP_OWNER, null); + workflowService.updateTask(currentTask.getId(), properties, null, null); + currentTask = workflowService.getTaskById(currentTask.getId()); + assertTrue(workflowService.isTaskClaimable(currentTask, USER1)); + + // Set the Pooled actors to USer2 and User3 + properties.clear(); + NodeRef person2 = personManager.get(USER2); + NodeRef person3 = personManager.get(USER3); + List actors = Arrays.asList(person2, person3); + properties.put(WorkflowModel.ASSOC_POOLED_ACTORS, (Serializable) actors); + currentTask = workflowService.updateTask(currentTask.getId(), properties, null, null); + taskProperties = currentTask.getProperties(); + Collection newActors = (Collection) taskProperties.get(WorkflowModel.ASSOC_POOLED_ACTORS); + assertEquals(2, newActors.size()); + assertTrue(newActors.contains(person2)); + assertTrue(newActors.contains(person3)); + + // ensure the task is not reassignable by any user + assertFalse(workflowService.isTaskReassignable(currentTask, USER1)); + assertFalse(workflowService.isTaskReassignable(currentTask, USER2)); + assertFalse(workflowService.isTaskReassignable(currentTask, USER3)); + + // ensure the task is not releasable by any user + assertFalse(workflowService.isTaskReleasable(currentTask, USER1)); + assertFalse(workflowService.isTaskReleasable(currentTask, USER2)); + assertFalse(workflowService.isTaskReleasable(currentTask, USER3)); + + // ensure the task is claimable by the pooled actors + assertTrue(workflowService.isTaskClaimable(currentTask, USER2)); + assertTrue(workflowService.isTaskClaimable(currentTask, USER3)); + + // ensure the task is not claimable by users who are not pooled actors + assertFalse(workflowService.isTaskClaimable(currentTask, USER1)); + + // ensure the task can be edited + assertFalse(workflowService.isTaskEditable(currentTask, USER1)); + assertTrue(workflowService.isTaskEditable(currentTask, USER2)); + assertTrue(workflowService.isTaskEditable(currentTask, USER3)); + + // Claim task for User3 + properties.clear(); + properties.put(ContentModel.PROP_OWNER, USER3); + currentTask = workflowService.updateTask(currentTask.getId(), properties, null, null); + taskProperties = currentTask.getProperties(); + + // Check if task is claimable + assertFalse(workflowService.isTaskClaimable(currentTask, USER1)); + assertFalse(workflowService.isTaskClaimable(currentTask, USER2)); + assertFalse(workflowService.isTaskClaimable(currentTask, USER3)); + + // Check if task is releasable + assertFalse(workflowService.isTaskReleasable(currentTask, USER1)); + assertFalse(workflowService.isTaskReleasable(currentTask, USER2)); + assertTrue(workflowService.isTaskReleasable(currentTask, USER3)); + + // Check if task is Editable + assertFalse(workflowService.isTaskEditable(currentTask, USER1)); + assertFalse(workflowService.isTaskEditable(currentTask, USER2)); + assertTrue(workflowService.isTaskEditable(currentTask, USER3)); + + // Check task cannot be Reassignable + assertFalse(workflowService.isTaskReassignable(currentTask, USER1)); + assertFalse(workflowService.isTaskReassignable(currentTask, USER2)); + assertFalse(workflowService.isTaskReassignable(currentTask, USER3)); + // cancel the workflow workflowService.cancelWorkflow(workflowInstanceId); } diff --git a/source/java/org/alfresco/repo/workflow/PackageManager.java b/source/java/org/alfresco/repo/workflow/PackageManager.java index 6f8029fd4b..0e1ab87619 100644 --- a/source/java/org/alfresco/repo/workflow/PackageManager.java +++ b/source/java/org/alfresco/repo/workflow/PackageManager.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Set; import org.alfresco.model.ContentModel; +import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.service.cmr.repository.ChildAssociationRef; @@ -65,16 +66,19 @@ public class PackageManager private final WorkflowService workflowService; private final NodeService nodeService; private final Log logger; + private final BehaviourFilter behaviourFilter; private final Set addItems = new HashSet(); private final Set removeItems = new HashSet(); public PackageManager(WorkflowService workflowService, NodeService nodeService, + BehaviourFilter behaviourFilter, Log logger) { this.workflowService = workflowService; this.nodeService = nodeService; + this.behaviourFilter =behaviourFilter; this.logger = logger ==null ? LOGGER : logger; } @@ -207,19 +211,28 @@ public class PackageManager } } - private void addPackageItems(final NodeRef packageRef) { for (NodeRef item : addItems) - { - String name = - (String) nodeService.getProperty(item, ContentModel.PROP_NAME); - if(name == null) - name = GUID.generate(); - String localName = QName.createValidLocalName(name); - QName qName = QName.createQName(CM_URL, localName); - nodeService.addChild(packageRef, item, PCKG_CONTAINS, qName); - } + { + String name = (String) nodeService.getProperty(item, ContentModel.PROP_NAME); + if (name == null) + { + name = GUID.generate(); + } + String localName = QName.createValidLocalName(name); + QName qName = QName.createQName(CM_URL, localName); + + behaviourFilter.disableBehaviour(item, ContentModel.ASPECT_AUDITABLE); + try + { + nodeService.addChild(packageRef, item, PCKG_CONTAINS, qName); + } + finally + { + behaviourFilter.enableBehaviour(item, ContentModel.ASPECT_AUDITABLE); + } + } } private List getCurrentItems(NodeRef packageRef) diff --git a/source/java/org/alfresco/repo/workflow/TaskUpdater.java b/source/java/org/alfresco/repo/workflow/TaskUpdater.java index 3f7796818a..4daf2ebcae 100644 --- a/source/java/org/alfresco/repo/workflow/TaskUpdater.java +++ b/source/java/org/alfresco/repo/workflow/TaskUpdater.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.workflow.WorkflowService; @@ -56,11 +57,12 @@ public class TaskUpdater public TaskUpdater(String taskId, WorkflowService workflowService, - NodeService nodeService) + NodeService nodeService, + BehaviourFilter behaviourFilter) { this.taskId = taskId; this.workflowService = workflowService; - this.packageMgr = new PackageManager(workflowService, nodeService, LOGGER); + this.packageMgr = new PackageManager(workflowService, nodeService, behaviourFilter, LOGGER); } diff --git a/source/java/org/alfresco/repo/workflow/WorkflowBuilder.java b/source/java/org/alfresco/repo/workflow/WorkflowBuilder.java index c5498700ac..2a77ad65b0 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowBuilder.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowBuilder.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.workflow.WorkflowDefinition; @@ -51,10 +52,13 @@ public class WorkflowBuilder private final Map params = new HashMap(); private NodeRef packageNode = null; - public WorkflowBuilder(WorkflowDefinition definition, WorkflowService workflowService, NodeService nodeService) + public WorkflowBuilder(WorkflowDefinition definition, + WorkflowService workflowService, + NodeService nodeService, + BehaviourFilter behaviourFilter) { this.workflowService = workflowService; - this.packageMgr = new PackageManager(workflowService, nodeService, null); + this.packageMgr = new PackageManager(workflowService, nodeService, behaviourFilter, null); this.definition = definition; } diff --git a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java index df9a4481a8..96dc6e0272 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java @@ -1000,32 +1000,48 @@ public class WorkflowServiceImpl implements WorkflowService * @param username The username to check * @return true if the user is a pooled actor, false otherwise */ + @SuppressWarnings("unchecked") private boolean isUserInPooledActors(WorkflowTask task, String username) { - // get groups that the current user has to belong (at least one of them) - final Collection actors = (Collection)task.getProperties().get(WorkflowModel.ASSOC_POOLED_ACTORS); - if (actors != null && !actors.isEmpty()) + // Get the pooled actors + Collection actors = (Collection)task.getProperties().get(WorkflowModel.ASSOC_POOLED_ACTORS); + if (actors != null) { - for (Object actor : actors) + for (NodeRef actor : actors) { - // retrieve the name of the group - Map props = nodeService.getProperties((NodeRef)actor); - String name = (String)props.get(ContentModel.PROP_AUTHORITY_NAME); - - // retrieve the users of the group - Set users = this.authorityService.getContainedAuthorities(AuthorityType.USER, name, false); - - // see if the user is one of the users in the group - if (users != null && !users.isEmpty() && users.contains(username)) + QName type = nodeService.getType(actor); + if (dictionaryService.isSubClass(type, ContentModel.TYPE_PERSON)) { - // they are a member of the group so stop looking! - return true; + Serializable name = nodeService.getProperty(actor, ContentModel.PROP_USERNAME); + if(name!=null && name.equals(username)) + { + return true; + } + } + else if (dictionaryService.isSubClass(type, ContentModel.TYPE_AUTHORITY_CONTAINER)) + { + if (isUserInGroup(username, actor)) + { + // The user is a member of the group + return true; + } } } } - return false; } + + private boolean isUserInGroup(String username, NodeRef group) + { + // Get the group name + String name = (String)nodeService.getProperty(group, ContentModel.PROP_AUTHORITY_NAME); + + // Get all group members + Set groupMembers = authorityService.getContainedAuthorities(AuthorityType.USER, name, false); + + // Chekc if the user is a group member. + return groupMembers != null && groupMembers.contains(username); + } /** * Determines if the given user is the owner of the given task or diff --git a/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoExecuteNodeJob.java b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoExecuteNodeJob.java new file mode 100644 index 0000000000..ae8c68dbde --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoExecuteNodeJob.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.jbpm; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.jbpm.JbpmContext; +import org.jbpm.graph.exe.Token; +import org.jbpm.job.ExecuteNodeJob; +import org.jbpm.taskmgmt.exe.TaskInstance; + +/** + * @since 3.4 + * @author Nick Smith + * + */ +public class AlfrescoExecuteNodeJob extends ExecuteNodeJob +{ + private static final long serialVersionUID = 6257575556379132535L; + + public AlfrescoExecuteNodeJob() + { + super(); + } + + public AlfrescoExecuteNodeJob(Token token) + { + super(token); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean execute(final JbpmContext jbpmContext) throws Exception + { + // establish authentication context + final TaskInstance taskInstance = getTaskInstance(); + String username = getActorId(taskInstance); + + // execute timer + return AuthenticationUtil.runAs(new RunAsWork() + { + @Override + public Boolean doWork() throws Exception + { + return AlfrescoExecuteNodeJob.super.execute(jbpmContext); + } + }, username); + } + + private String getActorId(TaskInstance taskInstance) + { + if (taskInstance != null) + { + String actorId = taskInstance.getActorId(); + if (actorId != null && actorId.length() > 0) + { + return actorId; + } + } + return AuthenticationUtil.getSystemUserName(); + } +} diff --git a/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoTaskNode.java b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoTaskNode.java new file mode 100644 index 0000000000..28f79e4505 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoTaskNode.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.jbpm; + +import java.util.Date; + +import org.jbpm.graph.def.Action; +import org.jbpm.graph.exe.Token; +import org.jbpm.graph.node.TaskNode; +import org.jbpm.job.ExecuteActionJob; +import org.jbpm.job.ExecuteNodeJob; + +/** + * @since 3.4 + * @author Nick Smith + * + */ +public class AlfrescoTaskNode extends TaskNode +{ + private static final long serialVersionUID = -5582345187516764993L; + + public AlfrescoTaskNode() + { + super(); + } + + public AlfrescoTaskNode(String name) + { + super(name); + } + +// /** +// * {@inheritDoc} +// */ +// @Override +// protected ExecuteNodeJob createAsyncContinuationJob(Token token) +// { +// AlfrescoExecuteNodeJob job = new AlfrescoExecuteNodeJob(token); +// job.setNode(this); +// job.setDueDate(new Date()); +// job.setExclusive(isAsyncExclusive); +// return job; +// } + +} diff --git a/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoTimer.java b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoTimer.java index 1dfa90196a..6f2a9f71b1 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoTimer.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoTimer.java @@ -33,6 +33,7 @@ import org.jbpm.taskmgmt.exe.TaskInstance; * task, the timer is executed unauthenticated. * * @author davidc + * @author Nick Smith */ public class AlfrescoTimer extends Timer { @@ -56,35 +57,23 @@ public class AlfrescoTimer extends Timer super(token); } - /* (non-Javadoc) - * @see org.jbpm.job.Job#execute(org.jbpm.JbpmContext) + /** + * {@inheritDoc} */ @Override public boolean execute(final JbpmContext jbpmContext) throws Exception { - boolean executeResult = false; - // establish authentication context - String username = null; final TaskInstance taskInstance = getTaskInstance(); - if (taskInstance != null) - { - String actorId = taskInstance.getActorId(); - if (actorId != null && actorId.length() > 0) - { - username = actorId; - } - } + String username = getActorId(taskInstance); // execute timer - executeResult = AuthenticationUtil.runAs(new RunAsWork() + return AuthenticationUtil.runAs(new RunAsWork() { - @SuppressWarnings("synthetic-access") public Boolean doWork() throws Exception { boolean deleteTimer = AlfrescoTimer.super.execute(jbpmContext); - // End the task if timer does not repeat. // Note the order is a little odd here as the task will be ended // after the token has been signalled to move to the next node. @@ -97,9 +86,20 @@ public class AlfrescoTimer extends Timer } return deleteTimer; } - }, (username == null) ? "system" : username); - - return executeResult; + }, username); + } + + private String getActorId(TaskInstance taskInstance) + { + if (taskInstance != null) + { + String actorId = taskInstance.getActorId(); + if (actorId != null && actorId.length() > 0) + { + return actorId; + } + } + return AuthenticationUtil.getSystemUserName(); } } diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JbpmWorkflowServiceIntegrationTest.java b/source/java/org/alfresco/repo/workflow/jbpm/JbpmWorkflowServiceIntegrationTest.java index 64f922f6f5..43032fed85 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JbpmWorkflowServiceIntegrationTest.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JbpmWorkflowServiceIntegrationTest.java @@ -18,7 +18,19 @@ */ package org.alfresco.repo.workflow.jbpm; +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.alfresco.repo.workflow.AbstractWorkflowServiceIntegrationTest; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskState; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; @@ -31,6 +43,72 @@ import org.alfresco.service.namespace.QName; public class JbpmWorkflowServiceIntegrationTest extends AbstractWorkflowServiceIntegrationTest { + @SuppressWarnings("deprecation") + public void disabledTestAsynchronousTaskExecutes() throws Exception + { + setComplete(); + endTransaction(); + + String defId = null; + String instanceId = null; + try + { + WorkflowDefinition def = deployDefinition(getAsyncAdhocPath()); + defId = def.getId(); + + // Create workflow parameters + Map params = new HashMap(); + Serializable wfPackage = workflowService.createPackage(null); + params.put(WorkflowModel.ASSOC_PACKAGE, wfPackage); + Date dueDate = new Date(); + params.put(WorkflowModel.PROP_WORKFLOW_DUE_DATE, dueDate); + params.put(WorkflowModel.PROP_WORKFLOW_PRIORITY, 1); + NodeRef assignee = personManager.get(USER2); + params.put(WorkflowModel.ASSOC_ASSIGNEE, assignee); + + WorkflowPath path = workflowService.startWorkflow(defId, params); + instanceId = path.getInstance().getId(); + + // End the Start Task. + List tasks = workflowService.getTasksForWorkflowPath(path.getId()); + assertEquals(1, tasks.size()); + WorkflowTask startTask = tasks.get(0); + workflowService.endTask(startTask.getId(), null); + + // Wait for async execution to occur. + Thread.sleep(1000); + + // Should move past the asynchronous adhoc task. + tasks = workflowService.getTasksForWorkflowPath(path.getId()); + assertEquals(1, tasks.size()); + WorkflowTask endTask = tasks.get(0); + assertEquals("wf:completedAdhocTask", endTask.getName()); + + // Check async task assigned to USER2 + tasks = workflowService.getAssignedTasks(USER2, WorkflowTaskState.IN_PROGRESS); + assertEquals(1, tasks.size()); + WorkflowTask adhocTask = tasks.get(0); + assertEquals("wf:adhocTask", adhocTask.getName()); + } + finally + { + if(instanceId != null) + { + workflowService.cancelWorkflow(instanceId); + } + if(defId != null) + { + workflowService.undeployDefinition(defId); + } + + } + } + + private String getAsyncAdhocPath() + { + return "jbpmresources/async_adhoc_processdefinition.xml"; + } + @Override protected String getEngine() { diff --git a/source/java/org/alfresco/repo/workflow/jbpm/jbpm.ExecuteNodeJob.hbm.xml b/source/java/org/alfresco/repo/workflow/jbpm/jbpm.ExecuteNodeJob.hbm.xml new file mode 100644 index 0000000000..7dfc6274cb --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/jbpm.ExecuteNodeJob.hbm.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/source/java/org/alfresco/repo/workflow/jbpm/jbpm.TaskNode.hbm.xml b/source/java/org/alfresco/repo/workflow/jbpm/jbpm.TaskNode.hbm.xml new file mode 100644 index 0000000000..3c8f247f3a --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/jbpm.TaskNode.hbm.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/source/java/org/alfresco/service/cmr/action/ExecutionSummary.java b/source/java/org/alfresco/service/cmr/action/ExecutionSummary.java index a2444dbd9f..dc83c3c9a1 100644 --- a/source/java/org/alfresco/service/cmr/action/ExecutionSummary.java +++ b/source/java/org/alfresco/service/cmr/action/ExecutionSummary.java @@ -66,4 +66,8 @@ public class ExecutionSummary { public int getExecutionInstance() { return executionInstance; } + + public String toString() { + return "Execution of " + actionType + " as " + executionInstance + " : " + actionId; + } } \ No newline at end of file diff --git a/source/java/org/alfresco/service/cmr/site/SiteService.java b/source/java/org/alfresco/service/cmr/site/SiteService.java index 6c3ef11b85..9af85bbdd1 100644 --- a/source/java/org/alfresco/service/cmr/site/SiteService.java +++ b/source/java/org/alfresco/service/cmr/site/SiteService.java @@ -250,13 +250,24 @@ public interface SiteService boolean hasContainer(String shortName, String componentId); /** - * Gets a list of all the currently available roles that a user can perform on a site + * Gets a list of all the currently available roles that a user can perform on + * all sites * * @return List list of available roles */ @NotAuditable List getSiteRoles(); + /** + * Gets a list of all the currently available roles that a user can perform on + * a specific site. This will generally only differ from {@link #getSiteRoles()} + * if your site is of a custom type. + * + * @return List list of available roles + */ + @NotAuditable + List getSiteRoles(String shortName); + /** * Gets the sites group. All members of the site are contained within this group. * @@ -267,7 +278,7 @@ public interface SiteService String getSiteGroup(String shortName); /** - * Gets the sites role group. All members assigned the given role will be memebers of + * Gets the sites role group. All members assigned the given role will be members of * the returned group. * * @param shortName site short name diff --git a/source/java/org/alfresco/service/cmr/usage/UsageService.java b/source/java/org/alfresco/service/cmr/usage/UsageService.java index 2e26e66933..f1d8f6f6dd 100644 --- a/source/java/org/alfresco/service/cmr/usage/UsageService.java +++ b/source/java/org/alfresco/service/cmr/usage/UsageService.java @@ -50,8 +50,7 @@ public interface UsageService */ @NotAuditable public long getAndRemoveTotalDeltaSize(NodeRef usageNodeRef); - - /** + /** * Get distinct set of usage delta nodes */ diff --git a/source/java/org/alfresco/util/schemadump/Main.java b/source/java/org/alfresco/util/schemadump/Main.java index 243e0c8278..b4306f6bb7 100644 --- a/source/java/org/alfresco/util/schemadump/Main.java +++ b/source/java/org/alfresco/util/schemadump/Main.java @@ -42,6 +42,7 @@ import javax.xml.transform.sax.TransformerHandler; import javax.xml.transform.stream.StreamResult; import org.alfresco.util.PropertyCheck; +import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.dialect.TypeNames; @@ -130,7 +131,7 @@ public class Main @SuppressWarnings("unchecked") private void init(final Dialect dialect) throws Exception { - this.scaleCharacters = dialect instanceof Oracle8iDialect; + this.scaleCharacters = (dialect instanceof Oracle8iDialect) || (dialect instanceof DB2Dialect); final Field typeNamesField = Dialect.class.getDeclaredField("typeNames"); typeNamesField.setAccessible(true); final TypeNames typeNames = (TypeNames) typeNamesField.get(dialect); diff --git a/source/java/org/alfresco/wcm/asset/AssetServiceImpl.java b/source/java/org/alfresco/wcm/asset/AssetServiceImpl.java index d2b95c99f1..524a654c23 100644 --- a/source/java/org/alfresco/wcm/asset/AssetServiceImpl.java +++ b/source/java/org/alfresco/wcm/asset/AssetServiceImpl.java @@ -55,7 +55,7 @@ import org.alfresco.service.namespace.QName; import org.alfresco.util.TempFileProvider; import org.alfresco.wcm.sandbox.SandboxConstants; import org.alfresco.wcm.util.WCMUtil; -import org.apache.tools.zip.ZipFile; +import org.apache.commons.compress.archivers.zip.ZipFile; import org.springframework.extensions.surf.util.ParameterCheck; /** @@ -589,7 +589,8 @@ public class AssetServiceImpl implements AssetService { // NOTE: This encoding allows us to workaround bug: // http://bugs.sun.com/bugdatabase/view_bug.do;:WuuT?bug_id=4820807 - ZipFile zipFile = new ZipFile(file, isHighByteZip ? "Cp437" : null); + // We also try to use the extra encoding information if present + ZipFile zipFile = new ZipFile(file, isHighByteZip ? "Cp437" : null, true); File alfTempDir = TempFileProvider.getTempDir(); // build a temp dir name based on the name of the file we are importing File tempDir = new File(alfTempDir.getPath() + File.separatorChar + file.getName() + "_unpack"); diff --git a/source/java/org/alfresco/wcm/webproject/WebProjectServiceImpl.java b/source/java/org/alfresco/wcm/webproject/WebProjectServiceImpl.java index 62a9e076d1..02a66f1705 100644 --- a/source/java/org/alfresco/wcm/webproject/WebProjectServiceImpl.java +++ b/source/java/org/alfresco/wcm/webproject/WebProjectServiceImpl.java @@ -91,9 +91,11 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService /** The web projects root node reference */ private NodeRef webProjectsRootNodeRef; // note: WCM is not currently MT-enabled (so this is OK) + private boolean isSetWebProjectsRootNodeRef; /** Services */ private NodeService nodeService; + private NamespaceService namespaceService; private SearchService searchService; private AVMService avmService; private AuthorityService authorityService; @@ -109,7 +111,12 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService { this.nodeService = nodeService; } - + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + public void setSearchService(SearchService searchService) { this.searchService = searchService; @@ -453,31 +460,30 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService */ public boolean hasWebProjectsRoot() { - boolean hasRoot = false; - - // Get the root 'web projects' folder - ResultSet resultSet = null; - try + return getWebProjectsRootOrNull() != null; + } + + private NodeRef getWebProjectsRootOrNull() + { + if (!this.isSetWebProjectsRootNodeRef) { - resultSet = this.searchService.query(WEBPROJECT_STORE, SearchService.LANGUAGE_LUCENE, "PATH:\""+getWebProjectsPath()+"\""); - if (resultSet.length() == 1) + // Get the root 'web projects' folder + List results = this.searchService.selectNodes(this.nodeService.getRootNode(WEBPROJECT_STORE), + getWebProjectsPath(), null, this.namespaceService, false); + int size = results.size(); + if (size > 1) { - hasRoot = true; + // More than one root web projects folder exits + throw new AlfrescoRuntimeException("More than one root 'Web Projects' folder exists"); } - else if (resultSet.length() > 1 && logger.isWarnEnabled()) + if (size > 0) { - logger.warn("More than one root 'Web Projects' folder exists"); + this.webProjectsRootNodeRef = results.get(0); } + this.isSetWebProjectsRootNodeRef = true; } - finally - { - if (resultSet != null) - { - resultSet.close(); - } - } - return hasRoot; + return this.webProjectsRootNodeRef; } /** @@ -487,35 +493,13 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService */ public NodeRef getWebProjectsRoot() { - if (this.webProjectsRootNodeRef == null) + NodeRef result = getWebProjectsRootOrNull(); + if (result == null) { - // Get the root 'web projects' folder - ResultSet resultSet = null; - try - { - resultSet = this.searchService.query(WEBPROJECT_STORE, SearchService.LANGUAGE_LUCENE, "PATH:\""+getWebProjectsPath()+"\""); - if (resultSet.length() == 0) - { - // No root web projects folder exists - throw new AlfrescoRuntimeException("No root 'Web Projects' folder exists (is WCM enabled ?)"); - } - else if (resultSet.length() != 1) - { - // More than one root web projects folder exits - throw new AlfrescoRuntimeException("More than one root 'Web Projects' folder exists"); - } - this.webProjectsRootNodeRef = resultSet.getNodeRef(0); - } - finally - { - if (resultSet != null) - { - resultSet.close(); - } - } + // No root web projects folder exists + throw new AlfrescoRuntimeException("No root 'Web Projects' folder exists (is WCM enabled ?)"); } - - return this.webProjectsRootNodeRef; + return result; } /* (non-Javadoc) diff --git a/source/test-resources/filesys/ContentDiskDriverTest1.docx b/source/test-resources/filesys/ContentDiskDriverTest1.docx new file mode 100644 index 0000000000000000000000000000000000000000..45189553dcd938806d9bab670a474fb73a62677f GIT binary patch literal 11302 zcmeHtbyQr-^6ntPoq^zl0KqK~T!TY^!QI_$aCZ-GK?4K`65JtJkl>Kuu7Tk0@FqFu z+?;UFd+WXR{`#$Z_bm2qX7Bo@ySi%E*VP4*hJghF-~b2!0DuhOiLuA$0|fx&Jpur* z0SM5V!nQVGV;iuplAE2eqc)?fwbhdxSZL~O05s(J{~rIr8mLZ?wCrTY2-*+2gU_|B zNIU^ag=GjQ8xoo!wGS7-x!n#SRUInLL;L!b(|P> zr-!1`e!oLuO1heSl1Ygf#1Q0+>rm_88{Bm|uGf5ofn8L~VyY-|-{zK}$o$k~DBzty zBC^;M;?6)1vequx3+pnMo_AFUxQZqCC3wxTe%VqIVo#Tfne#f=>2p}K#2kb4#M&>X zh7tPWdYqiBpO|)dl;#Om4u7kiil!`Wd*RH&xHnjUS;o0oe_jgX*>J9JN@;${L3YZPZEwC+?JoksGDRq z3!_8nP!D&OcN)|jo7O&?R^g{lELnLKQt7@-RRHqz+c!H2=Jhg;-UQE$dsjV@a6xlD z=DYW2ONo!enQyDO7_)QU@L;tarIMjv+P*c|y7za%H$RtaJcwql9TwI0(%W`D?#2~> znB4t63;^_--;%@<)Ez_a$U@u_8RECP4#rlFOpHIy|Ml1ZU?=_C&?Dn!rC@|mg7+US z>i2qa%pyG+*CPCNOme$^~s8Z{}l5?vqB)tj@;Cw|=>aw1Bj)JO#XPW%}yl z*;8qH;)|B9)^!o^AoX6&H~yxm;zQ-;HntN?ys-&yc_Uehb5^Q?g(&!+ny{AVY6bOv zRBDVNl0;ODjvXL8jVx~b*mI%^^CmTaMjh4MbYHNhM~z)~0!^EPz#GYAf2uzWrx#w| zcAC+E)=IM5Apx$u;F7*r*s$%9dz(? zj$2O|4=I^CJ+7C>wQxWclNmoGg8jRXEY`fL#em#tg#iGF00>Ynwhl&2e>jSft)Y`O zB+UJYZ+|%r6eP$&p8n51O5)|DKQUu;?fWm14b10hJH;A*X$dV?EnRw%CVQ_JpJ1f2 zTgL&co$Y$3bo6|Tp;5qCtg)mL9W74v{Wbl}J-}u`M zyU4M*CnQOM7u}t+S@Y?kR2cS$yOp{SGx&FlifgO1x`x<@IV=Ex33=kDMg85D(v@Uv zXPHr2(5HNu!Ez;RLT~UT!>!d_2tXzC=pdsR+-G6_d7lQoZ>fEZB+*67Bdo(c_hX-K zopu(FPGfq4%s8yr*Xd#CDwJv$%5|Bd<8k_50&9T;3|3Q!&>mCXH{~`FSnKV+QTXyx zAfnH+pEkpJ(JP_IJAlM$4l0{bp+;BS93A)k^>oN+$hwM~c;AZ4`o}$ox^sz@RKeJ*5**AS!1--qQZ+xx8dtvZRoMoBO;5g_rVEU|p_W-;qmi4@xpf#6rZ{vHj4$7vCplv(b(5e?hfO&QtZt!+U8spVUu+7WR2DJt*YkQh zX+Im0AT^nsl!xA_rn5mKsb@1F8i~^!ZbyT0fw0+aW!|8osU6ND!<(>#*SkOv{a$Va z6o10*u)+sd78d8;W@?ZviESFx4hQcP6{9$93{7k>apo)679G6y@d%?+p;qm__dxs_|Uof32DIbkWC<|8i{Jqwb_?bF5M^`qBqVWedJo8CR<1z!{MeI$qSwSQt7^w-d&6 zaeO<78ZxPC`o4tPyQ2E-;Fhj*JUD_QJ9KnS53Q7VX5aKHa&Jo2uG9rV+oT1)XRyB+ zb&hlu-mwTncn#j-Y7RO8)ktj=_Px1O_f!b=-ij*)^#sZRcl$ehp0)l4vO#1?-4Ugl z7IZ}ve3snv@0aIgnF69c=$XU<7h4z0ovZKy8ZU>6tkVP6m6=*^cZPdNa|JTJkB>$l zkGraO-(Pi7wcgygy}%eVeKr)qly!5q^Esl7|Ms>?9OppBPu%z-rduQ@Z4F`f7!Sy* zVoym$ZvPRd6XDBI_#j0BXZsuJN4J^bL??qZCaEq&T-={(mWZL&bCF!V9ELGnF~Y)f zaME3Jrl9s5hPbtqH1x%fX;a-eB|9uO3>3O|4L#Mmnj)}!T3aNMoLJGN#Sd*nh*-|? zWPxasFM(r@H@-1QyL$@Xr<{_n{H6I-HW@{UU(v}t&m*ULV-itoj^drFvW0TzY*pm} z@5EU=<{m-RDA9{FKpRMkwUKhjok>lk8cVd2l8EXJQ(P$3+pctBtR+W#0G7ZLZ}F8- zuh~cfcuC7Hwu35Y=Rfx6S405ANt8yL{kWLO>n?;iI_x!qh({=B4FzK0F>>&B?Uf%b zuG<7}x*WjB;x=BRQNC`HD}$>u-wb6##^7VcoJj^1WgroKS=;Yp$puAQqiAVy1w&t~ zZ1>>Ap4Q^7$V|l-J=eb$xzrBiQ3p3^!Jh1vHE6oV?O-AtUq*e?{_Lgd&~oqjSvz6a zl%cw62FY0U9JQ8)ox!2GT$n_$8_uGy7uEcY5#qJTNQh9&P&%5Vy@3?D>%jNM0OUvP zmN84;$%}G?0u{~5w=o1mP!UxJ{XAz?M~{eOuo!W)tnDCk;>>S1>t*gmJy)fP6Wci3e7^R z=mZ@%n4rRA4@hM1+MF-UF?1t8-g=6^>}$k7=01E~Xp^4M6W)O6JN5n`mVYh7pa!ch z>De{h+|Wwi^QLZS=Z5ozPttC%!_9feXCmB{dFko&LgLnC<61WEy*=i$gq^9lB&)z? zMyKHrb%e*lAF*;X>1orkC9G z%P?%+5Gq9aUp(fOs;`Ex7jl-iePeM>u>Cgy=v4#kRW!$!y)Zd$#13sNh7Hi?yl z>qBMXl9$K|?})nZS(Q(`p*bGW`JMTqPu4?|XOvk-BjeZ?zBCuP{pj1>{uM10w>~1s z9D%m}75n`ouCMpjL+S+2N|)OT&dft$gKi~6{bTs3rJr5NlBo=zF-pf72fh+X{FL|} z$3p}Sk?=b~ebKoyx+uo?{5M9%ZA-Gm1;$8IU58n(^PaE&8f=)@S^;bzcE2HV)s?*S{*kIGK+34_l{Ov_> zdk&e@@<;eBf%1F#W!7usm$rq%AJJ=Y+C$)EP&(jpuHvkDV;Ezc?O>zgiL2Z5B-Ou| zl*ZR;d{o!9E6_aP)3;)nR=@F*HGDjn+CTKUD}v40M89Xk+3M~4_9`kxU~u=PP&a3{ z=NPSz(@L(#(k4UwViq%{40AgNrQh3Aq7+`=wK}4~v8dL6q&(u2ql{aSMP>>lnEi|L zI2wb&<~F8|KZ01g@~%x9Gw@cxeBbw?1sDg4%NetfO!F$7&mZOUg@RGJrJM~kk!!VV zdpDg^G*g!1xX1S~YiRuxJ9mMEP6k%)_ELS*Bjs@pMYHU!E_&1S%z3p?vxt&i`W1Z1 z?RN3`K4X)Jp<&xAk0D%~=;&uCV0PzvjI7BQ8y6;p`KUk#q-qlc?MRGUU zkbE@Z%AZA4=J#l({xm#NSsxw)bR)IsRR(-*l%OS)?O!r)afC z&nj_}7c%zz5YviT{rZ$?OVnilI?aXwu`S0T&BiJ%iY;zI%B&19n5Q3Bcryk@dMD-< zv{xO#?P^6mfm@-4xi&w`0?U-N_T65%e$^3W+gJCwdK!-dJ0Zd##yg(E8ge~o_SHbeq5#p7aZTgKWIYP@B%16qnUPcFeaah4fua4X^uM)mS zrOryeI8c6iI_0x=YOsdO2d|s7EdbJIh1ovTLk%WZiwZ&1|l;a<;kGqIpGq4=K@Fkk^L_YR21P(zjjB#_0Wp_3!n)>_Tl>dzIX zIoR0xC&~No@)Dw@zmCA+_Hbwx6u70PC7y)2j3HNy*ezFe+~%y#J1mHocX{&y9VL4y(~4{(N-wA*U%Qiv!w7uaAIAkoM73nl$0}1uL?9tS4&3dkNZ;IxS8!bSGge-uBv=h?(OLV#G%TuFp-$E+e z)McaVCCtOlC6jB7<4u@k?og?VWO!HcUy;#My=+d&+weAhY`$VwE_;kI8ubY+0IRab zV0;X=QVvV%6T#_~@G!{M&AU!_eTmdQqL%wqw_jP?)L6VHc?(-YJ6515id0Ab9x~*A zUnOy2)*0cz0{|CX0027ViJ$9fN3fff@z2mwohI-2g%hJ^(RSlwK+paNyB{n!Y(9{a z8%B$4cdp8SE()%vZyL#Z|I4C@h6k;6?!Ki4g}n8_P{~K6i&3XKWnjVFNJCF&hQbPv`Ke zc=)*ca--(LZyFXp1NTauYH{EgtJ26@bc!XsM@GHr6dl>P_%n%(pJT!;U5f1)=OUa=gg$ifvoy2}FjlQp)^`YGfh~@f&Xm?-6;ziv zQS6HqGQ9Yu9dSB{X&fAr=^+_jF+NpkM-P|mQOop?ee`zGN zdfH{256&OBed>8T^ZcznP2KmZa6|L>O0yx2u84pUbmoArWwy>W!(-trMYv%fPW4dWB4tmeS`dGDR)rRYpo;YZ~9k_ zs7O;$K{@ zI@e(~_;b9;e4mAqennRyWptpLzoPS1I@vRdWjW$=JC<{5sDup|tDn9;&V?~2@@^4a z4z|T_8A3(be(Y4m((VvIS@)RTGWFyv)$Xjdkmy^sBg>#h@R(^juv1-Hr76q2$8Im_e zR=YZ|or6Ovv(oS|I~N6shv>C`L=-=+GsvRSr2`L>NASf&U^_v64GM-P*r!*w@9y9e z16<+QopzNAmz<^vKni1v`083G?apOWGGarX<}uBUR$RZYSXylfFKuZrmFhko3yI1F zUba3?3>1M15`K2ljhWB4!xyn=_&P-R^tJTYw87@$DjQsyseGZT&|*btf#+pO5e3hYxJBQ4zY6@nz1j|C9zG5N}4z z{@Gw$xQ#jdl-6co88G>^$Q4`g0^wWnM|kC~Gyvo)#ikXG+{(STiF-_SSz|L)V|icP%(c+r(6s4rgdtniL9vCRO!u={95MsJxs-H2K(WiDMPihLmu z6LNwb;e-hPY}g&{g+x*Ur4j?Lm@AOPPxVP zDqxfh-436XGwZ@da6kn{^WuEMAB7;|{W$$!!M-?p2n*@wPuM?|fPn2-{zHeo*n;)p zoccf1O#V~X%PaHf_;SsN^tJ4+GNs`hP@j_E(TjduC*YcfanB;+>IyQeDWIIPgc^$d zP|~Ym!;t9VE`&cA!%?Lg-S5TtwK)W$7)ZN=LN0W+v!JXE&#NhgdZ}xq61o}Y&+DZN z95D2`_c=16)&UrX1tLR*kG%f7}do#Cb zUjvr8s*yP{l!&$~GIqI0T`|y=aRx7ZX8psnmIpMo_#HiOzI&b_nIP}^GMQCwiBv-| zJ>{euUlysrS=g8Dh&pge);R0W)kF_@A@wcX>locub+19ZiG$~s-8tGdWVk-0+3^h8 z^HW#T))S)Hm&sSWC)cG1=2cR2LTndyu`{5y3W1x&H^^?DRO#yVK8*2e#chU`V*4|s zGHWySO;kNwf1clV8<+4oYtlT5n1JDS}$)j2p*7$Ojx`j{_6%2>?k(|70CZhPDpI zf9&43L72z??2V1cIBCmOW?;}c%rR-;+3S)(e&*LmUq+OXVOI5}T#CT)g*7A+bu!2M z;tmEJZ>#VQ#JICwM-M4`9P4`=p|RlaCnXG9NVUekJ}>~ zB|N;jgmR)g#9BW)5}w$XZ4$(VKhq;A+^l^Tdn4)JWo7M- zccGa7sxldo^k`%f?j-uQ;8gYf25v+`Z<>1>@`OB($Z6_C{ z0n4FcclpEO*ShSFCuOt+63tA%Nk@dBdZ3(Vy;has6L29y{SDT4RCT1Al% z_BI3g!~QoC{Qty%D7$s#88Q%{`)+mSDqu70oKsb|E03csb zZa_csGmt{UY2b3}Qjx5c!l$Hsq;hrD^%?(tLs*yl<)im*f(_L-Idh+|p43rm3kWbU zuyT!43N)~ByeJb{>}_j%B|yVUL&Yl4LS6AB7s~=qIkK{9G{mF(buxtL{{vNp_*Gg2 z#L?g(TR&KTQB@O2v5b%ZppI@n;5h?bK` zlmt5K4_s1I=K#d-yS0*fHR`@&*7Z#pn#<5)yjNFt6?mjjWjw158hkAn||1aT^q&u?a-#D8H>_Nq{ICS4aV&G|gE*e8k8!~9=qz#AGsJ|#d!t-bL`I)~oI0tg z@VF+(9azZp0soph7h?m(zh$u^huSSVnNhkAJ@of6z`7Zy%u*5x)VWL}r%+nr<|q|L z6xIu8Coim$=iPx>7l-@ky5`pW@@&sh4=SzS3L)`|Hlo_O3GnT|l|N>?Ohkmg5DV(O zsNRxRJ#ZJRx_U38EWv}X$I_qSNPzoFr!GDAJ9`y{?P)^Vs@rR*MNUr&3dg2eFDL)v zI*n}U0VE#{HxW8|MO?H(4dJn?ZAwza?wQ_cgKz;?11_Y;qV|UScQw&2|2SAy%iBCB z$UyrcIXetUUgBQ?LEp~qk9YtJ^*=oxKnDK*eWWYM$U!E&h5V4TXrT@){3NCMNj0MV zlHpk6F3~$1!q{i>ab#!v_LHS_D`K)|jyE?qSgtoV;G-aZYRio+qx5%(1m?)bMQ)9s zv&7HMv2vJ$Mkbbc`Sc`6FHdnwnw>UtJ7O?`5Ra>0j}GD?S9Xg9vLvD^OKlTk(wyQ{ z8Na7sES=*pdzxl4P29O3n%`!y=H5|lnC{heABU?_d)2`%z=<_L*3)f{8QWUtz*bJ; zJ16$mar{XuS-qSdB`u04bP2MasTbx2Lph_bC_{5hY{}{uc)b8%X8ISYaEZ(#4$2nP z+Q`p6_SbpV@a)adYrO>P3&pNW_~rUYWqsi1jlM>1@@~uD<7@c?YJdcftJ#@?&=7W@ z+d9}~Q#wUa5&4C71Bc7)8?e(@o=gRql7R{v1Y3=kju=jy|UbNUap zh#=|T0e&xo`a3{9_u7!Z3(i3j-T$Xbk%veRYXW{@F(Up$g}_7cha1Je z#MK~y=x0YBZW}*@c$lL91wjUhRDa}D{`RVSDE@mU^OrCHzy;a4{*}@CLz};>Qoge{AB)~b5CtE<-gc6F&Jz`)`H-~osL0N^3O5A#4c015yofCB(<0Ep1~ z5>AfpmX7Ym8s5&9ZicKN2m1$ku+a3m0BFeb|2_VLEl`y#XV<}w8G0D{4I$sIJmpkH zJ~CV4g*k~0a?|@0KCHq+EYKe^TDwh+_%K5; z85ZtQTT`sRIL)EK3S|lP#CNR;>J95Un>1-W!NhrA!(pv1dDrTltjryYBC_bPU4gZ(nTrp##9k+EKF}C6cVSgB2L<-(ba#8 zCDzl@=g=_tuA&_GZNC?T`$eUTCw+D(3XRkJZg%7ZLD6x#1{|y=Fwp(BI@U=u0=gD8%LN z?qC2azr`(i0&(pZ$Q>m}SfW7U*4Wk3-i?j*`}x1|`X3ymzYIMlab6xq95w6^ZrQZg zpLZUaa(s44_LUdqXPA)mJt!~q!GgKwru+vDD)UBLr`rR{Q<=e;6M}Dr;7d()Cv#^q zO=OnsKn}Ih2r9b0`WZpiXfnfPKu4!3Hlc*%S3)ryMR{wLVdB&xlvbQ&`39lA%^D5y zq;g2=aS4MYXE8+$%{`~uFd6A@=5*13)`w!%JvuzbQ|N}g#7~i}4rc};@p=(Von~3h z813b{U1Mz-&X|eq*+*!y6U>T{c~*dVlV_55#?jA1nUP*0MUH&&^T>kHGs1A6&GYUl z6{L8m%Z%^udm|BC$z~%0L9l<1k>%ZVyJUqsQ|ZnmDcM46zm^xb zX1?pSkq^=!iuWOJhXweCgXQKi1+g_vfe=)f7C5gwCFJ6E^8iWbfhEeMKs2@wYT1CJ zQ%&g%V^rMZMfX4{0ut&8$wldiTstmC=jm6mysy7Vt1|o32)q{63^hxkwp)Em9&ZS$ z@PF8L80~A~z1+2N*JP;6L;NyZgt*F`<}oaFRCeg6LcdVz)qoIlU_qYv;ZeW(QkHs~<2*ZR z6UK}vVgsF42IWu;UssulAR7JD0SvBgO|^U;ZJ*1>QVL5M-5MyVvS`ahpTh*o`uYHl z4AKG&WP2$BH|fYKXj{wqsHXwfLi+01l(pQ`CYDlhk4A6yZ@e;J6^oxr&&kHOhn+kd zW0ls%m%_~E5*loii*AE&T*Xpc#t*yMUK)ZW?8%>FDp@P9Gqsme=2973nm>``;FJd; zj&{N_yaFc2zonwOc;dm`5e&fNgjd<)dXe$Ss9eoRw-ko(GN%1sa>jbTAS5XSZ8pN1)SY2H1hM5rd35t zo`Es_qFIK=$#=~K$cW^#x~}!5i1Y*WN?s^r7z)lF8TH^^fqG^RtR$~tqvzi$CBAIJ z9<4`|MOjeS~M?G&aKR z^Hp+mOBhVH!J4@IK3aY#ftA-Qb*)C!y=iazn zPB$l)zCpnD-<=}0x@0XC9sp1w0RZs+aSC@EO9xA~A7_s5FPS}EWv6)_+!l-(uLtf< z=5(IK2p&;0<(VvsB%O}oSGp}#BW(DlY zMoY_N)DpXNj1qZfqDn-lv9H!|?#L+$i>42HP|2_#GAPd(eRUm@pg#5%ODfx6BtK^> z@s?%Gf=xRPscK?KSgMY_SO!N;Yf730nfOyqyUa%?%TK>ZEx_o|G1_8~GjSZ0iot7) za%RB1MEulk53JYHH;m#?6iQwp>|G*`D^wm;Njl|mT@!{cjZE}uwKmI@!?6x+gGca) zjaQ$wgeEhaI)A3z8W*V}|%Ebez)H*s%udJr(gVWR5277VmGJ8;z zvj%&~1xX2$KAb;{i7ol`=d*?;AMp`D*?+Nol01HZ^` z{aJ%0N^d{fTUO}g!CO#}xLsDy*_yk}wvohl@opc*4qMfNcURDQ*R;2fzA}|ehDGz{ zMvQHkpqG%%9a>MI^rltr%U=?=PTLasg$3Er=P6VYevxE}swP}s&%*$qS?G+x76RqF zXTs?Z)aG*ZLf(0HwNk-4xz{yk7`smVW^`LapYg@UR{*th)VTfJTxjU1T$=GZ5I>$o4N)iawPh&4ea(p? zJso1OO7|k=7wD{BA%oh?M+W)3j$nZ>BO~+hvb^$UpblJz1q?LwOl7_>rhD_rwcBo) zsdew0`{{Inqj7p#n&gl@I58AtjvXaQIW7p5aM9%+eFun_$w$ax2cpfE;bLmDUZgDBG(ve z4CH5{s=XBFZFkWNB^#w?G#8CWz|14uchQ7f-gFH6CDSKds&xsKj%C6>ZR8^5^3zt`rYyeXx)e-=8+s zntq&}uD=*zC7<0e|ct8cQ}mB?g6HS~3)dAJ5O@S&Nf>R)s{%kSCHUohgNQzLMxjB)i0Q?=L(tq%e2iWLQ|Xt|)cC zyveZ~rJqn#;$O}pR@-ahLHM>a+(XMrzd2?4adhLHO|p1KbKj>@XxVol>Qkg{=45Xr zbXrPHkXGt>DMLGdt)71Q7MGFZR(l}9Hg{NC-b4A-W`rHlvOCpP&Kt|ePVXd|F{<&} z!r>KB+u`%B6CH%&S>rvOVPliXs@e+VbU#{^B-Q9O>l!=1)jtw8wP%^tz4cczCm2c} z81Cze=JK>M?V0kle^uC4NvDn*)_o=3&DZTW&KTgamhZdriKTA&B|D8GdmArJ;Hxvz zG@)l3wWLGiu`PdOH%?ROmTM4*dkO)wf3X`kOLupmqqW<25X;h>SDRKJL75VT&e?FLu^53f$?&R!>SlV$0@@JKUl_BQ)z^P55-fGgi(rE zDQ=DZL0&u!o(J)_hWRI2rY6i0*lD=bXcwI!+t!E6*)@?5r}EN~R34{EzFF0Z?!-k~ zHKEPI*CW=Qm3!z-SrmnDafyV0ksTIW%@~>2wj;|(sZx@d5zH;4Hm!Q-%@EB(*DhVj zXC$4$UPf33eD}yG_O6uJXcCmLXeYPF_r-Ic>*n&b>gOw4SM1Ry)a6#2tgf$Mq%eG2K9H)X_?ZQ;6q@VxK}2Z$n%; zb5OHeAuR(6k6$Gh1hG_l#anAJa?L@(gC3DkN5gaBrha-N9%yUFP@FnApK_8}W!IO| z9dh%)SqFDUiNv~siho&wJWQqlXk-9-*zhNGM*Z+8lC8t zyePW5rP=x@M`}DwigN~KRv_e(e=2eF5xx=)WSq!-WT1}dxLEdzK-twlKs5Rj*Y(j3Qb%Apn$`pkpS7^MT0X1XBvPgR=-Io4lt{X3DcqZjGu5FXn6YU+N6peX zTNPu;$g&pa1|3u%jTw@DVaFQpttQyv1*mGg&5LZ$pfR0Xp7P5lnA)#MFslzKu#!Hb zSkKYIeN6Y2?`5G6%@3ya7FLvy{@ks!D5Q7GD8B4P;Vn#3r`xmoV!Z)_-oVO6m^4c~ zK(c6+-OMd+NXCA(yeoptcMsH=O0F+ZJiDH_ioLkrf1TgK)R$&2cQqRfyd;<)XyeL7 zJfGYrL{EDnV4@>{vxj~1jO2;D?Qx1$)y7eLSmkBi=P{EZ6Sms($P#te?p-9}WD}_6 z;18P0S>T5O_zIcH1-X3jvpL)A@)T!*oVNDTxQ;C;#Oen*p%~dGmpv}$2!CRB?xOrY z?T}ERhfscU$O6*b!_D2vLC3-V?^Ptw-O}L)yZi4F5@Mx)j*yYIC}<8;_!aPqVDdut zFbFeY2c(PN7+JSc?f!_f3#gt!e|?Ux%*4FMfpqn)U{SQa_#kaoooiHsn?B~b4}}bj z==R|xK5leuQ#M0hAl;)q@MfOys{L!`;|-jmJV_q*$b#&tJzCUMCV9dEi(Jy;y4?%y25E==D`I6a`ahM%}fmp{4VK@0Y}lsdp?@29|;ThyU9X!jdTuIuHA zqla%a0o7)_UqETFJL?HpMER2!RFAwTT7?Ww6XFGg0D8brFZe$HDy~k>ZatX80ekS@CW1kR^85VSBSgnlDd@zC;6;VZ&6rkyM)GCk z2XDvV1?S{W$KWT!tK96(_(M*ye)(xTL06Ff4m%8PURhIGwkBzDuzC}?=-PQt3iqKv zDXS_`GO@sTOh?tKl|D$%${tk4VAs<&d2@13=;ti&TXTo#FXWXN-A%hr zPV0ilqAjms)wDO_Y}YfjC;ptNB|iXw0Wr#tHM5($x4q>LOsUFLb^FMN z*|Y4l)g0V&ILZ?UD**cjmrnr3;Nkv4r5RH!d{6%@vcuuWWn0Ul4`lP;YHwR+{TqFt z_ML~?&JY;vfPI^&`@y{dQ8|YqJn#t>S<$H#mXWc)&p9KiE7iCTwLnVrk^$l-J7ZunaaS3Qs?&GR;o++ zs|Vz0q7gNOYJ zFKC>#KeT-M^fu-Nyd(2t3-R@{F3UIWZw9|o`hA^y^vZ>ycDFLh9GFyL16JZ?Oue(W z@W~#yeD(E)?n!yOS;0ji)P=R%U=Vej=#rGHwL0pPx9!rGnfviNmENJj==+nwydPdk zcXbCSY(z${iuX$+gJYmiTW~`1moVTJ_affIh=G}n-*zZA5<0GJI^(1@c5Io6ETCYH zyuxy+SM8JcQHgzSaH6?wdfh-}=@2K4e|By|t>_dd{rvRYra%(XfQK|-8*62#nwx}) zSNHa?kZ?cv;48ZD$fDfHr%TR#%Z7G;q_P{OhGP)*WWmC$7RlbU1iVO-I`FlB)If^g zzd?sp@y*A~C8EGx`xggE+*j+KwOI8*ycs!#FHy6unabrYj@QdYGjqYp1 z_DqkEb#%ubV6IE_V$F;Bw(MT!?nKlyjE4G|z@w6*%{7>&mVn1D{q#KD`Ml*F>2|Ig z$Beg?^4a=MZgJAJEFcpu8c9Po2(c@*MHVt2`57m=-G) z8k}XCp|X)MVKZ}jNsaAxj{KDRWt%Q;8!xYXPKEh#ZaylqAnEg<=vWbaPZiq=uXaK# zK{4*BkT&8s)u@>I?g72V{ojr{S>WG|e>1G~;#bzU0?1?TkX_#xWq$MeBp1D<%0R$y zYmhkbOggisSctKtmrnbTkb_+7k}%g4FCL0m3z;Z4_0}d}`YI1?6GP@r0G>oaX2x85RkncjP8-*PS7@_$F$FWZ`6S< zB)V~&prcFBjMR(P6anR%ZNcv$*HtP~2R{0xy4?UU2#0l8eA1phobvG#3F6*ZK{OY!=mK-8=%*hpk%_)Ccomhg1x7Iz2nfvv6~sAdX2D&dz# zpI<^bm|s-Ui1*T0%O`iUEMC+pymiGi6*%ORb3VK9KAI(a20ZOZ_G$bAuY{o)hq8K1 z?dyzq%XJ~hS5e92m8x&&EY_RznejPbmA?vw4^xBmb9weYKLrRALlbZ4GGIO^>gDR7 zzJZ9F-|ep7IkFYX!810SiXF)+C^kwyrpZ;wa=fKOrS{k(j~9C91Nr(G;oS1uQT|^r zoU{+>Wx%|GUwJ&^K;a{G;f-$R(0-j=;GGAg^N(L#3!UDS904ok7sRjGGlHi}x!?W7OqC=@s5>cDh8I&^k#wB`t#91dTu^0=OE#6!u(zUMp` zN6!Rl)``+ILxNAS-8Gc)A&Qkv2V{8*o%|o80o1;!gyG{Tvm> z4njUUAZs%$z&ZeuaQ?wM*vy?=E&tk~UxqM`|J9opS!#-Q&>Wae1lJU)3)6dy6BRZg zj5&+0 zHylKUDCF?tN&4*UOk~k9dm|Q?(L#zs&W_efVB(M;qpPX8h*0_V;BBHGLD|j?`7#2aACTFZh z^Z*f?c&0a6AZLz`eFxPwxH4syhrzay;KdJ>pSgMR?h>C4<``$btA7O4f<`2fRejY? zJd+tcD~4{PFQ+r(=N~M_Qv+`vVV~D7kSWnxJkLG?T0_PlEwP@Pw_MN&erX59;KtyK zX40pRR}M$n=S`j=uVHk^?2?|Io+%_6Ehp<`mj3V@Mu6(D>DWSqrdU7O(&+T0LX_=B zQ?>I;y1`LV(wkL>cff-CeEtFOAvTj<1oP)lzpD$2sb$v;UWTF=GHT`IZU+uHg> zl!23hj#IRWzWhNxwk@G%Ohx5bxNrCK7Z8U3he1LDDl-}qVF-|Y9c;)GKRButq!LEM z)Xe@zh!rF*I|i}ib|1djBa=O(gS&eYYm@bHz>xU>>bXtlAX`)a?ZdKzB_ zZNj0lr}=7;2C|v9O(TG;xGSy6kJVW?wF2{%eUTMY`!pIePm4o5O$V>2tMULccijf5 zy?V8~Shf8#=0HV8%tBpFkSLs5rRBV#$`CjGt`AJ8kj^Zw{3yJXWHkyd(mKcTF-OUn z;97@|rRjeBmWgVeBh`f~ygdGNW>kvgX{cHQSIZ7T88&+85lu23W7csiPE9P|_OWA6 zBQ}k_WV_Et1`h7cm%3o<83rzL+u~zjjbvqaIPM8hncF|?sfZW#xf&9xh$NmLF&z1% za6+1LL}wak!Xm6GVy3(H;Uztmt&ow|#oJ7KS?}!UdFS7nB0w*DBJIUgxmM!rCt!sl zGh|!rPwC1($PhXOSO$L?nI#Q0B)YO@k{c`B)$&XP0Ny`MuZ^Z8LzyMb%`J%WmA^>P&kkpuL+C7GDj@zLMu zNsM2ArlCOUp6i`8ixLHy@goyRxtJe*tB&*f<6!eRstw#A108@M0VX6N@h>DWb$0#> z2wJ(P{z2A9`6$`+6Kk3#w3_FApW|){MA7XV;OG;eH>Zl~ z4yB&xgVe0zIdG{5qPtP1$*W??aMf%Kk{pas$S`M+o>lK>vEL*SWBWb3CK2fXGwu6R zjIQoEIh&nJbk#UNPJA~oP`KX*K<W%e(w7X{rUlK$MQz> zrcnM_(gId~Es-eN9VA)&7ivI3vp^#7&xM8er|b`9hAIlbEBL*_=~o4Hka~_^>YeTj z{$BR+tKb5puRmVx|3eYTeNFeb`+w?UMf$_$|9$cM8^b@vB_J^Lqa*jXiSMhppOXKn z0u_Q&f2C4>dC}b$|2={EQy2gsgX~oQOl19KkKfao_jUcA2K!YP6~V8%eou + + + + + + + + + + + + + #{bpm_assignee} + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/test-resources/quick/quick.chinese.msg b/source/test-resources/quick/quick.chinese.msg new file mode 100644 index 0000000000000000000000000000000000000000..a88555e93f18df6dd49a580331b5d8d459b349cc GIT binary patch literal 48129 zcmeHw2|yFq*6@Ug3aB8UZEZ_Nw6(TEG6{>|5=aP(3JII2wX%eOSR@cov@XG|c6$Z2 zc6nV81#7L!=GM&sYWG(}echk9)b740wfb6H=RapAg9%GADf;=o|MTd{ojY^xJ@?$@ z+&kyaJsfErda|m1#3`Z+OeTcHRoac{Z*^Z_bxn8?#3YEj0A8hO8Xw_vxV#1bi)tW= z(8IqHf+SQ#0fYu(F*YS{7*4o$wz)5)@)(Rgf*_ihUm}_f*k8@!OZnz=9|AMVt- zvbT|c0g*|}2X1*pHgCRMruPv70oUA0?q-+p{NA%~{2S!nv@tOq<+px}{ELYNL>By( z5V;mP(?K2;U=W}L?y0Pk*j(Qa@*{r|?qX>3u$KrH;C9Qi$q@dD39SIf`~bj#fNp^9 zfDwRJB(x{Qy{+!~!1Zja_-$~FJnjG-0eC0iU4VB3`T>dnNx+eS{(z$ZQIFVzaX=FQ z7zmgQXpa`3jR%?;R&?=jEeFI-iOoL*a3Y`r5Zf*a5NkRH5X%w^VI1HTzy|=+022V! zfD-^UfQf)ffJmPLVJhHsz)--MfKvfy0X_(bG}9o2;J*g`C&z!&j7!Wo7z%YjjKv=U zdjHKC|C$>x{;?nR1l;&PNqo=9Z%uQv$3NE*`$3^l4`8+QH2yu{{vg1?fH-cr@h^kx zVSqUPQU9YJ9u4?c$NxQ$_Flj-fcF7nH@P1W$Dj51N1Y!8h|`D^5XTl`FkFWK;`qby z9|jl>h|g|8_ZqO@|75)8AJp^MUYI)s4guSOBeKt5%7?#=KIQVY7xLqe^KK*`{sI?Z zhVvy~`SXDm=T?N?<(CjL=*4{Jf8>q8=4$`PyQiJy#H}^09iYPhkyd-EChS?A3_9!{8$4jmbiys)c?3H#PuUz z`SCpw-{TPM@n>g0$NV|j`SQniF+THOD9`?f>t7sCeEH-1;BY_$`||Uve`n>#_fkIX z9||jJKJAaP;JZCv`SG2cU;E#f^-mga`N#8-pRsYEeS@$3XdiGazY=<17&H1Dt-qXY zf3#CLo_~=)zx6NLchIK8SAMkJIG%ryKd%3-zy3#ijbr_f{Lu#IQ2#JCEs+!QCjzbG z@rV3z{ppZDv;M*NcE0tGb}PsIPX+vWt-l$*Tpxt2d4Xg7tAg2=-}*xc9MOKrSAMkN z;TjO3cm0d}d5wRk*PqjXFWLza?DNO*@3{T_As@7v@Rc9Oz9asu3{6YXw&A)KNe`wev8l>f7CkMKS%2yL0X`qTQ6Q{P@1^X#QpS^Xh+2>p!#?p$zWR;E2j&&kWG!YbwQ{6xEIh9aQhxyXibCs`?~+@to-Ys0MFxu0nmH@LrHIp z{iy=@I$Hm+`iGzX#rXvHI{3DKInZ+JKeUB%h*J`GjrMHrnhWtge=s1o|ARUVZ69d+ z;r4&E!Mm@%N|3?C}xQ<8bH1<6#XE1~zfT4g`C*1xI z>U-Sl#cx({&sz={0T>CW08|1-0jdC_0b>9;xiOq-iN{+)b(iNHEv~D$YiB~ORk|Fj z>pZJ#F24e}rw1$qECMtD76X<5E&yBzSPHlZa53N`fJ*?E0xknw4rl}{1B}eiG3bi5 zdNMvahRo3$=4Y0WCA#90yn9 zpIT`d+Jsm=35pbxq)yd{)v0hTRwhhJQYqs!ajJxrWQg4wW56HhFofRaNB+F*KLVUfJ0bK||15vh z0*>_`u4f_uu?!G6gestzVWH~l{NW zQbh(Sf+$6(BvKI;6rzllMJqy7N-9zs78(^cJ|d@NzC;=rodW-30+s4`lp5BR(2=)| zB#3FVq8AJ*l>M;^dbUAZm>U)>PznV>9vKC>ltd=`V94Gaou@=PUU1o^+DE8$A+&Sd zn~HTMb17MfP%=hP=baJyfMIxANrhx z9N_bw%PJqQp&^BaqTwrsnrQDt7}Nt5PC?Dn%2ZN=%KLKFMFHFDTI!lmw(KF4XyxN_9dCthXllm*^Iii078f&!0fT zQqKUjKPf9OH<-zj$s-{$UNy--MpvLSWR~a+R!QTNyRHvOSR)qa=?ln^z~I2pkYH(G z2$NAtT*@@nWM`JO6UA&AkVG775lNwrnu(%~3UC%f@z^@85>v0E`U3ER(&Be#8XAip|x=auG5d*W-D@{c38{#mnNXTc72 zW8&^DThn$Y>`-s}W82T$KYgtfo~1T^yY=VlhxRS4iLHNR=f^wh-pm|Faz*G?trAK1 zYHirEZ#l|epK$Q=I$d={OI&qfbz#e^uP?8Ot@--x-%7); zCf9VdB^#bo$Lfu%}V(R9CG|Ve76YYS)6Q7_Qq|#x1K3t;d_Q z*CgBHYParc>&z21gROjhmZD~9m7=M!X2z}s)v<5CTNQ&UoeU$ZW7)2coiVm5`i&po z`VwiXVkjxx=$xyrS5@w0t8!cXp~mVrcjKAcV98W7Z`M*itWnmka`rS-xY?U4h2v`Id0bfrr!>&EVtQD@ zTSfuTao1xN?kwEeh${5woySdP=r8wmsZ*!13>%k8@SAE4O9_6z|oX{hz*_L5>|q z?$6ybtxml)?bV+e-mYom*mSEJJB$4u`c&_&d@Y|&;W(a7h;}WPYx9R~D_b(<*{i$0 zh;$u={B=PfFKtk_Z|k=^s_H&`?U?E3w@d1dp&o*j%3HGzq>^HCYeD0YeFX(yXf_Ryc4(^0Sg3+uE#VgHZ2-hK6#8q@2i;3X`wCbsvE)_Z@w_BLke zud3d2acU$6^q!B+EN54}$7kl{_nwba%=HZEA$J4OQ zYD5|sWN8+Q0_wUF+1+5zn87uijT+)p2)pfIs|)%(%4%Y()4KFMUK+}78H28G53#mj zfMR7|wmVzKwtB%P2Nu=tzm~SQHMQCdQMPeY^5*M`$pl>~nWUeeS>Qj}vhuR-`Q5Kc zn~~A|IeUk+v|Vq{EmW4;b3her|2g}Vt%wjlq+}>Bs`0$2!gx_p68mIq>Fv{4KLtC0 zb+r4n#mLkBLH9c(<)BGP%0X*7UwQ`(Pf`xroaIP4Xl}4gbQjn94oNv^WUy*j(|5mS z3u3EQaF4`d%oKBbBw}Vi$f;ewTg3k@HQ>{jHNKuK3MR+;t{-xV6jJSih`{M&y_5?2 zR_8lZI?n9A*?q+zi7Y%|yeLc@F26ECJuy8XCEM*WaSp0wxH5Gf^ZG?STAT!Z3#hX%FPOYX7mD=BQ8GA0d<`Q&p0nApJ$B<%-fNcg{JaZqdqH<}|3< zLOz@Bod}_p%nTw=O%Yy+`&2h9Q*=t7&j`>$Rc^<+7k$?`bWe6LKd#9yxA=p_r1^ z1*Qi5I=-LU)hpMdY(RwTkpUHV6=&WbF)7?bn3EUq4HcvlgYVvLuR}LKK3@`~k0m{a`bfR45;l`iD@4CCL}GO=D%JLjKu5(M1xjTpYdVB-*Yu z8SxX`n?wXTwnCF3I6_e$JdhC{B&yKFFWermfgZjhaKt0870C5dmXRUH9~RMy4Radu z8zt*z)fkDTWY8g_(mVBFrYO$5{Mgjb$s{dZa`K61@-{xHlO3T}OE>Gs(AuQvB%h=D z(udxE5*k3&LZ=B8nW?L^NurahgR|d#I_*mNIm%y4o{G{^7uFqrVhR1Xe!+%CO(ku? zD&5A}{&C8gflXAX5xUaysbyhT!>4XqKOxt9gIc+UD$3Yo){R|vCgOa=T}gh5;wjNd zm*bK)y7;t(#-+w6%Gb{xd8O!F(6r#7ju(Mmy*{Xerl{${b>FNM>&1ogqA1x(di!S; zrmgQO&rk-6T02y^*tlfc`q0#)iH4_jd5W56gN<_6>B7W4$*0TLZ~bgrei*$rHSx^T z`fa~Fduh*evEJmV13IZ;+RAik{eg97C}kF%SiLkl==i#=)wNNple3-)HO052OB1DA zw=LWc--NnI-iHn9(G6o1qLYGk)C!00Rk@*YrT{7tAP0gCo&CQx|^-C`ZPBkZ1 zHdiP{wh;GfN|R*cOIzr}2E#E~+|CZw$S0OfGsc(GQ|^gLArA_OkVDP#mPp@q^zyRG zvj@cMz6n?#6w_4MTpQYAI@o&XL}*;IEL<8$zvmkm`(F9`l^@i8Xp$?GBk3Grtk2&D z%vz=N|I+)T{%=LeA6*wAk5nttmHMbsQcU|rz9#!3RkP*5uhm00`J7(cP8H=T)T>lS zPpt^qnj{e8qj8VDXvpbe- zq|WHP)1~xBD@p&Dq`4cbyf6D-eeR=}E03;v!zEY>4K{YuHyNJ~GfrPAZ~Q~2ZjuRp z6McLokd__$#Isd&MBLAGRDCS{xc+kK!P1IHM)!jYD3V*m2-;!yzsHAAd3uX?IT#(ID}0Q(W`!rmKO^)o&VpdCNl=2a08_ zLG5&~IfOp!cj3qW!3pQKY#r!AzZ`KV{rlLmq6@!$v)S{jhsE?`m5;yfV%Vi#a_mw= z)E?u1Vv5$yo4)Xk0)6SSBieB#`RgN>Ns2U?`Ps|$lQI@*hGs1C*Q`&JgtQ$#`>@U+ zUzhuG#O0Bn#YGmKTOU=j=zM9JB&De?H?wf=mUSsj`DEe6l7Q4zSrd<>HZ7`~zT)@d z=?6;<Nc2dUlc6z!&lQBa*-LSZ5<|peTyiPuwpSo(c)G&K(PJRjT+e4`anPKzf zlhfCx6+JkNp0j(#==9|?pGjZ-V88UzOZ6vp`T2{a^D}-vImcv5Z_Q}WF=f%&1BR6a zzZ^kln{{-qXTCX4oIT+Ay8V&#!=ilge0i}*pPoJ7`@%)*Ohsx#da-`7dO>;ifbp9* z(UlQpLiwhM!rRZQKOifzW%OrF#?iGEMp-9nt28^M){-q z+2V)fb7B_G&ppv3FEuVIUtIY&(-Kqm07^zn+MYB&ML+Gi!CWEE9`a-*y}tbg@iX+Z zo`s?!@e8$;<@AfKFSRc;zCv&EtP*Xdx5(+G?aR!|&%e@aR16dt71_n zhQ6uxUAm=}KG^2f07?EpG*I@ z-fa9e{X*sEBKiyQm!W04c}Kn$|4V*C-JVWgHJ>t`E9JFO?T->0eC)%P+Uj zQ};Iypxr!&m}w93N3@sckaSPImvLzM(R6ya-q$p&b$I)K^uHOGFW#}|M{4Ii)Xt66 zu6}=L)kZq9@ZN}o7ZUmkP?#t8>#2EZS-aa_->CIr0``%@+`;GZRC19{S zeqln!CfXWMNzNu}{ryEZYs>ZgB7)nVwfB+3V0Y_RdTtkvw0w^+*q!Ws*MG1(yIt?r z;|4rQ4t8fY&E4#8fDd-J+OuX$)8}pG!S2rQ3`3eOo4I5X$!&_l!yb1lxq2e6!L@Bi ze26=agWdnyCLu8Gwy!tX{eCx!)wrS82|Y}u!Qy$x>b zB{nfGfpoCn(LE!s+p{Ht0`jxfv*a>cdBDC6HQk#znp6d#{kGHaYRMuHc#=25w8`cl zM565xuc5GL!^6uUlC&OIou)Dv6x=_`P3k6d3vvr~8|)t97V0+MEzFIQK*Wur+}tRs z8zo~RL2eY1P$6zqs2erjjS7Pp{=23V*-sKBQbBY|yhPgl#EiCJQj$)aSC|JMu@s9T z#(Xf$ocCcep{PK~wcMP)>H~kl^*B3pv-uAMKg$RLLi`j~B>DzG@C9^%6LWZ$N#Ny6 zm^?+`X%?Rlu6Q<2fQMM&dKmmPj-Ok#;_f@{`=j(l?elO*k8wx`&q#Piat96Jx+mz~ z;?A9sQgI)W_mnx0d#V}XeJVmvfBT>aPA{@d5_AE${AOwgC;YcH=j}S6(-@1G67oU61$_Cx00_0Qwtp7KM@-uv$k^e>D&rg2z z|LBmvAP(}22IMRM-+A(vF#i7>@^@PP7kKig82_i&pa1h#{Nb|Cz_mT!`tLKtE*bQN z{|d-!B_QhL)s~R+GtxDDs+Z^M;r<4|X8<<>J`4C9VAu4$#W!2gTFY|l`o`CQ2l#fv z^#BJ#@V;Xm_aMgF#D(Bp$NIS+c-p~u03fbMBES;@#uv@{b0_}I8yCR@CXW6Igv5*e z{lEhcKIhgiE&@3d$Mg8?^9Uw>I}-;R2+VFsf9<)V%X4Ly=TTjr$1riUtziBxfIwl_2882kwoV+fAA|^iElD{8#;IU1=uuJ;VF3%U)Ja-Y? zG00Zl5lsAAeca8&x$^rFeoXvYc|}Z|E3XG37=rn60CZ)xae+-7){i!EXbmPk*PcRR z023c&$xuiL8T#-~*cGRc=x3AOkGPvzpR)C}-(5(IV$u(=IDl{wjA7#0kRRLr_c8J5 zOdQv@V{PIdgsV;dLSixVoaK)6{h8-z9%I{Y024+I_<~h z=R(N;jtJZSFrrKR&Mxu0y2KYUajrg~4rj9Y;fTQUx-oG}@j-sKGHZT*ggX<5>SpSj zN#x|u&9vY3)qc=d2UPj!e=CsjoE4FyE66R$TpXy?&*xo(w&mxYtdr0-z4QLR`uhHl z1?fxsWA$Cz&FB>Cf9#^K^~cA4X-~$!+K;)40y_kww{V=ZdQ-^6xp~Ns5HRs;dg_uV z>H$<^Lc&)dB!)5b7Nj+javh$vgz>grqF+G=`2jOWm4|os}%yTY3 z_-mF-ew^-sGKO*2kPYZjnu&Wd<-zz>CeGzAB#M~)kQM>t$-wmJOP7_IPPfiaNc_pf zIsSgcB__`0=OVbw#JP2ikm%@=-rObrYnS*>UE%~&zgRXxY(Vo4FDP4sfA0$>zrP6m zne@Ebb>p&vNsl{Pe+^QY^wu@+Ts|wA^sdAZCgk4A`R5^E(pe^B{>eHig-m+1@clLL zV$utUBqqe3=p5Y6q;oEfH8q!>D+ebJS01i@xO(Q=i)(kT-no9{Y=0a`vTfhQ7Ao4h zY`3l**MNQdBl;h{{`MEN|DYa3u*aXL{YQ`h`EYN_Or{Qgo`>h$NVmsd0v=>}_}>%U z3;B%!&kWj z`|Zzb|HEni{=iyhnewe|ayW}|ah#F4Yc53p&v-oqLR=@|JL*tCZ$KYF{ALBeN5MBg ze7(m$hWl3dZg(f(U4VB3`T>dnNx+eSnCB=6M+4pi$c?A_;Cd|J{eTYujsrwH0r6;;tq8PU?|{tz%amYK=e`bp&7jsQOSJAoSM%*q+bp?>{*G_b>2m33Vqz z@BDA>-~Yhzf9vl*@mv34zvF;yRr|lw;Bl&jq__6R`j^7&#bf;;!1;U%AeOZDcJb znbx><+>_&^n|!*+ne*RbSd0&V1q}M{ ze|l@vkX!!yo%7%C(0_*<{ddU1`)4q27)o7T>#`-cF;mTf>;aOX6|fdu3HU1DYCz83 z(NlOMzyE5o@UDSyt5rE(gX`^9@t(@nmwU~ScMIS_z(atC0pA70JpV6%N62x;xyyuB z^ThT^=E-z~r^UHTD0+A4bnfyCdUv_z+~q%Gf^wAoIOi^3-P5Fu%8d3_wK2|Ju7Y9$?EMG@`Yh~8bsgv706oVzRo?=Du(U6v?1RBhng zWs39xNpy9HNlGcfJxcr@#<`24<16&;!a8?J1b+>CL#9MB&RwG0a#U?}ymWN4s3}OFlvR>!&?*gCSr5gn2$|WEniaPKox7OHQ}K=QkIp>|&RtY3>N88S?os+@ zr)^HF@;Ml{B9zWkxg66eU zY7F5&MTSLv%{q7aCwO<6u$y!4QjFeR(78**kKo*;A}@dnnp>Zrw?w&R(2*rd&bf=v zp!Ja_EzVs|fp-_ixl7D=ssF#hyGvSCZXq~#318DcaWHsyS$b({1v+<`l;RaX-e+t@ zikFRNmq%tzu3V*l z3_ZIns(3nTzN&l}>)FNd8MykG;^K3(cueM^a*Z-7&Opy>TC?$|c`eV< zF~L+(w#Gd7i;WwiHYq;_&n^pppW<>k-sSlQ@az)rI;CTSBE9X(f|nW3F0TN6vV!sK zGP7~xGi%xn?ZxJji{RO1*V3Mpl6pSnq$Tqm;bTxyO}Ff{nro6?|3ab*r?bu zvoTedwtjvYQL?K2*k+p}wz!^EKrtZSFUn#08C=EKCe`m!p)sl$TG z!x5^s!*%y+mbl2qFEP*`_%B=~i>u!p_2tt68gZhWj{oP>eynSkL*~O5WDn4>r=z}T zHf>xOu>O$xaQeIYufQ$D)1mQ2Z65`G*!odB{jvEUbgO6T?!3mS2P^i?kRz zK5+4gNToT7j`sW|Mie`S6#GT)ICJ8jlw`W4=GEpc2fs_Bit;;>WpOKC{x&gWf71_< z-z1OlM>BqD+^tY>KyUuHPk&OHN z;a><%>>>Ia{aNF;<=<6)zb#IbRz8)YkBHksKltRUvZLkNWAj=vHq%BGb!Ki$*6G$3 z(toN;zRFbHa-~CfxpnOmvbP(+r3?7y8N2BR$=7FRNtIf`;p9SECiv6bCjTl*cI;^T zG4pZS=q~z9^tt#8`I6!#5nt|-(O<0}l<=?3Q(F3clItqn{I z&iWTPb$LOWHR{q!s>?4%lC&&*#mRfTFTaC6U7o*`e2^Y|n_I2B$@fz1DWdg~aLQR) zE&rG4L~DEd$+KrXv$B2`wVxaH#@q|y4%zQfy4`0Vl+*KCX06LF%NdZ?$j=<*lk@kMlb;ES1~zrL@fX&XJHHe5X+eWHGn@q^S$Zw?aE z50_^84_UeC{7CcF-&T+O@DBP1!=Q%?+MiPY?ZE*@>uKXE`IVJZYF2Gq5LwXwk-P$Z zR_Tf|ZAt#d3uPyZE=*6%&(nlZbmcy?pyf+I^$(X*44n%S+h zv!-k2$Y*CQ8@Vt2z{c~#kK_Zfzagvi(=5_7s%^TdZ8y>DypokF;zLRnHyf*TgGZSGBJ`SzW(IN)K7FMmi5Jg1pv@dcEyYGrg96-17<1KK)bjzp0;2 zFW1u*##c0ZDz?)*Je%aZ#IE{N!V=t}2 z?}2bvj3036ulM&K8m1f`L?ROoB3zhoFp-k4`W*dB`+3m?@rUids4u4fs;950 zFPES2Y!{sz^Jr>e(`gZXM*NNZth!(Oclz&*Ka~GiN&i&)Tlw#mS89hC|7GnJ6y12yB{@|e zlQB+`@_6#a84GGVhNfi2UQXGvELB1TWU!K1euadWSAWYjnz4k$P@A|PF_4MtfCgxB znQH^R+n>?$%>Vq~49FGN%Ql(!64?g#_gkLz#vk{eB#iyr@%JZaPoD|Mm;WZ9#XU;| z`~2BmF#J2vy}|wP1v%~|~ifc$X3nlFDm|HqF%zxKzy@gyhs z9{^es5W#-^)>-?txzuSldd`A@=A|HVRn zvjO?me;v^B>HiY=it$E_zgph%-z=I z{WI&YbSL=l;LZQWwEyc?{Nb`s=jZzR{Y+oa|2gyh1EY0|3A!?J-5l7|HtzXLZZO!*7^UNd;Z^p5LwRgvfuCtiT;fJ^3{JYeFXp3 zJoukr;&=Chvl{TFANJY*&EJ3C?DPM)4$~5PqJStNbj)u6TrDOFAvU6iXIdg3{L&S} zKLR|Z5X4-dE&=LrIFTcUyIQ!TBjz(Sfe_}w9RuWF444Cyfeg3#K&uB&DJ77b9{xQH z@j^(M2Xy$0p74s{X(r@Q$gGv8LavL6QlK>eU*yh0IkOLdCw~4KJk6vJ{%eUL)l*D* zJbCojU=fpEOuWd15yWFpzPaBO-aGih2qxXVe961fIeXr=YX;o8-8-0a+)j*V!m(Y+ zdX01EfG5O%8R*KKzUq~<#|PYb;$ViaKk+OR;z=)lz@1Cx&LKxI>HYX61}g4+Gs&d8 zn@}?0{lpq3?0w&WW5Au4Rx$MBxZAu#Zr_bNZ_PWQg)ofaJBs)_6XH&Wb1<4oM>?0t zirR-se;4r_6NXzo;dgl_lkQF;oC!zsOJqf*VA9<|tY$(yMd%z1W76G51To=A=Mn?8 zFOzOK5z2)3Ih}qelWrIxVZwWejZBEA9{IuD481q8jtM6aZjTY*4FWCd{NOGo-B6-} z3C9xyne=#?k{{g0r1K&|m~adc$AoywlOJ&V!2GDYQ*ryro($g+L?jc6h*eA|>7Jm= z9d2JbilKjic$5k8R4PB<_P0e$I$xrY2?L1@Oo*pg?SqD)^C0|~(1*~K6W_WqCtU5P z#tnD=kz`|j<|Z{#w9ntIsM4#SFWBp{lSeNPLFeXi5u5k z|K{d9uD@{OhMRXd{mZonr&qat#nmr2?{e+TwHv3mIeoy*@7y|pTW@gfQ^C~x_|9?n z91{=k98cEs5q@8B7gRj952tUrd7u040%tCq|G9C*jmK){zKUzM>lXI5{}>Dw^=QWa zg%+^h`QrXJ+THM6HDCK1R1tGN9EGsY|HjzARsml;1BcKXf3$p~-Jh@h4`oMSbG84w e1s-#n#Xf(2{vReoei49t`Bx!-_A?p6{r>@UTLQBH literal 0 HcmV?d00001