From 890d3795cea4748ec2ee69f25efa02393445dc0c Mon Sep 17 00:00:00 2001 From: Dave Ward Date: Wed, 9 Jun 2010 14:53:24 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20V3.3=20to=20HEAD=20=20=20=2020440:=20R?= =?UTF-8?q?M:=20CapabilitiesTest.testDestroyRecordsCapability=20(MS=20SQL?= =?UTF-8?q?=20Server=20build)=20-=20use=20non-public=20nodeService=20=20?= =?UTF-8?q?=20=2020441:=20Include=20virtual=20tomcat=20in=20installers=20?= =?UTF-8?q?=20=20=2020442:=20Change=20bitrock=20builder=20version=20to=20u?= =?UTF-8?q?se.=20=20=20=2020443:=20Merged=20BRANCHES/DEV/V3.3-BUG-FIX=20to?= =?UTF-8?q?=20BRANCHES/V3.3:=20(Fixed=20tabs=20and=20removed=20'svn:execut?= =?UTF-8?q?able'=20and=20'svn:eol-style')=20=20=20=20=20=20=2020384:=20Mer?= =?UTF-8?q?ged=20BRANCHES/DEV/BELARUS/HEAD-2010=5F04=5F28=20to=20BRANCHES/?= =?UTF-8?q?DEV/V3.3-BUG-FIX:=20=20=20=20=20=20=20=20=20=2020271:=20ALF-803?= =?UTF-8?q?:=20Asset=20Service=20Improvements=20=20=20=20=20=20=2020386:?= =?UTF-8?q?=20Merged=20V2.2=20to=20V3.3-BUG-FIX=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=2020385:=20Merged=20DEV/BELARUS/V2.2-2010=5F04=5F06=20to=20V2.?= =?UTF-8?q?2=20=20=20=20=20=20=20=20=20=20=20=20=2020379:=20V2.2-ALF-1888?= =?UTF-8?q?=20AssociationQuery=20was=20corrected=20to=20filter=20...=20=20?= =?UTF-8?q?=20=20=20=20=2020387:=20Version=20Migrator=20(ALF-1000)=20-=20a?= =?UTF-8?q?pprox=20x3=20boost=20(policies=20ignore=20version2=20store)=20?= =?UTF-8?q?=20=20=20=20=20=2020388:=20Merged=20BRANCHES/DEV/BELARUS/HEAD-2?= =?UTF-8?q?010=5F04=5F28=20to=20BRANCHES/DEV/V3.3-BUG-FIX:=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=2020372:=20ALF-897:=20It=20is=20impossible=20to=20?= =?UTF-8?q?create=20content=20when=20default=20value=20selected=20in=20Con?= =?UTF-8?q?tentHeadlineBackground=20field=20for=20intranet=5Frssi=5Flandin?= =?UTF-8?q?g=5Ftemplate=20web-form=20(also=20fixes=20ALF-2798=20&=20ALF-79?= =?UTF-8?q?1)=20=20=20=20=20=20=2020389:=20Merged=20BRANCHES/DEV/BELARUS/H?= =?UTF-8?q?EAD-2010=5F04=5F28=20to=20BRANCHES/DEV/V3.3-BUG-FIX:=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2020374:=20ALF-2723:=20WCM=20-=20Http=20500?= =?UTF-8?q?=20creating=20content=20via=20webform=20=20=20=20=20=20=2020394?= =?UTF-8?q?:=20Fix=20for=20ALF-2257=20-=20It's=20impossible=20to=20find=20?= =?UTF-8?q?and=20add=20group=20at=20Records=20Manage=20Permissions=20page?= =?UTF-8?q?=20=20=20=20=20=20=2020396:=20Fixed=20ALF-2956=20"XSS=20attack?= =?UTF-8?q?=20is=20made=20when=20a=20rule=20is=20being=20deleted"=20=20=20?= =?UTF-8?q?=20=20=20=2020397:=20Fix=20for=20ALF-922:=20Mysql=20does=20not?= =?UTF-8?q?=20support=20unique=20keys=20that=20contain=20nulls=20as=20one?= =?UTF-8?q?=20would=20expect=20=20=20=20=20=20=2020402:=20ALF-2186=20:=20R?= =?UTF-8?q?ules=20not=20being=20fired=20on=20datalist=20items=20-=20becaus?= =?UTF-8?q?e=20it's=20a=20zero=20byte=20file=3F=20=20=20=20=20=20=2020404:?= =?UTF-8?q?=20Fixed=20ALF-2109=20"Rule=20doesn't=20apply=20to=20the=20file?= =?UTF-8?q?s=20in=20sub-folders=20when=20'Run=20rule=20for=20this=20folder?= =?UTF-8?q?=20and=20its=20subfolders'=20action=20was=20performed"=20=20=20?= =?UTF-8?q?=20=20=20=2020406:=20Fix=20for=20ALF-2985=20-=20Share=20documen?= =?UTF-8?q?t=20library=20throws=20error=20if=20document=20modifier=20or=20?= =?UTF-8?q?creator=20is=20deleted=20from=20Alfresco=20=20=20=20=20=20=2020?= =?UTF-8?q?409:=20Improved=20FormServiceImplTest,=20added=20more=20content?= =?UTF-8?q?=20related=20tests=20and=20some=20edge=20case=20tests=20using?= =?UTF-8?q?=20the=20FDK=20model=20(this=20test=20needs=20to=20be=20manuall?= =?UTF-8?q?y=20enabled=20though=20as=20the=20FDK=20model=20is=20not=20avai?= =?UTF-8?q?lable=20by=20default)=20=20=20=20=20=20=2020414:=20Merged=20DEV?= =?UTF-8?q?/BELARUS/HEAD-2010=5F04=5F28=20to=20DEV/V3.3-BUG-FIX=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2020401:=20ALF-2616:=20Serious=20Web=20Form?= =?UTF-8?q?=20layout=20performance=20issues=20on=20IE8.=20This=20fix=20con?= =?UTF-8?q?tains:=20=20=20=20=20=20=2020427:=20Merged=20DEV/BELARUS/HEAD-2?= =?UTF-8?q?010=5F04=5F28=20to=20DEV/V3.3-BUG-FIX=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=2020042:=20ALF-1523:=20Failed=20Kerberos=20SSO=20auth=20doe?= =?UTF-8?q?sn't=20fail=20through,=20simply=20returns=20a=20blank=20page=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=2020323:=20ALF-1523:=20Failed=20Kerber?= =?UTF-8?q?os=20SSO=20auth=20doesn't=20fail=20through,=20simply=20returns?= =?UTF-8?q?=20a=20blank=20page=20=20=20=20=20=20=2020428:=20Merged=20DEV/B?= =?UTF-8?q?ELARUS/HEAD-2010=5F04=5F28=20to=20DEV/V3.3-BUG-FIX=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2020417:=20ALF-736:=20WebDAV=20Folder=20Renamin?= =?UTF-8?q?g=20fails=20on=20Mac=20but=20works=20on=20Windows.=20=20=20=20?= =?UTF-8?q?=20=20=2020430:=20Fix=20for=20ALF-2313=20-=20Accessing=20a=20Do?= =?UTF-8?q?clib=20folder=20in=20Share=20which=20has=20a=20link=20to=20a=20?= =?UTF-8?q?deleted=20node=20fails=20=20=20=20=20=20=2020431:=20Version=20M?= =?UTF-8?q?igrator=20(ALF-1000)=20-=20migrate=201st=20batch=20independentl?= =?UTF-8?q?y=20=20=20=20=20=20=2020432:=20Fix=20for=20ALF-2327=20-=20Can?= =?UTF-8?q?=20not=20have=20more=20than=20one=20Transfer=20Step=20in=20a=20?= =?UTF-8?q?disposal=20schedule=20=20=20=20=20=20=2020438:=20ALF-479:=20Mer?= =?UTF-8?q?ged=20DEV/BELARUS/V3.2-2010=5F01=5F11=20to=20DEV/V3.3-BUG-FIX?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=2018448:=20ETHREEOH-4044:=20Externa?= =?UTF-8?q?l=20Authentication=20Subsystem=20does=20not=20perform=20user=20?= =?UTF-8?q?mapping=20for=20WebDAV=20requests=20=20=20=2020444:=20Fix=20for?= =?UTF-8?q?=20ConcurrentModificationException=20in=20file=20server=20quota?= =?UTF-8?q?=20manager.=20ALF-2970.=20=20=20=2020445:=20Merged=20HEAD=20to?= =?UTF-8?q?=20BRANCHES/V3.3:=20(RECORD=20ONLY)=20=20=20=20=20=20=2020413:?= =?UTF-8?q?=20Added=20clean=20of=20quickr=20project=20=20=20=2020446:=20Ch?= =?UTF-8?q?anged=20version=20to=203.3.1dev=20=20=20=2020447:=20Merged=20V3?= =?UTF-8?q?.3-BUG-FIX=20to=20V3.3=20=20=20=20=20=20=2020294:=20Fixes:=20AL?= =?UTF-8?q?F-1020=20&=20ALF-1013=20for=20all=20views=20except=20agenda.=20?= =?UTF-8?q?=20=20=2020451:=20Merged=20V2.2=20to=20V3.3=20=20=20=20=20=20?= =?UTF-8?q?=2020450:=20Merged=20DEV/BELARUS/V2.2-2010=5F04=5F06=20to=20V2.?= =?UTF-8?q?2=20=20=20=20=20=20=20=20=20=2020412:=20ALF-1887:=20too=20easy?= =?UTF-8?q?=20to=20break=20alfresco=20-=20one=20can=20remove=20the=20guest?= =?UTF-8?q?=20user=20and=20recreate=20it=20but=20then=20access=20to=20RSS?= =?UTF-8?q?=20is=20broken=20=20=20=20=20=20=20=20=20=20=20=20=20-=20Person?= =?UTF-8?q?ServiceImpl.beforeDeleteNode=20prohibits=20attempts=20to=20dele?= =?UTF-8?q?te=20a=20guest=20user.=20=20=20=2020452:=20Fix=20for=20transact?= =?UTF-8?q?ion=20error=20from=20NFS=20server=20file=20expiry=20thread.=20A?= =?UTF-8?q?LF-3016.=20=20=20=2020458:=20ALF-2729=20-=20rationalise=20(and?= =?UTF-8?q?=20deprecate)=20VersionLabelComparator=20=20=20=2020460:=20Fix?= =?UTF-8?q?=20for=20ALF-2430=20=20=20=20=20=20=20-=20AVM=20nodes=20are=20n?= =?UTF-8?q?ot=20checked=20for=20exclusion=20-=20the=20default=20ACLEntryVo?= =?UTF-8?q?ter=20will=20always=20vote=20for=20AVM=20=20=20=20=20=20=20-=20?= =?UTF-8?q?avoids=20embedded=20AVM=20permission=20checks=20for=20getType/g?= =?UTF-8?q?etAspect=20and=20anything=20else=20that=20may=20be=20added=20?= =?UTF-8?q?=20=20=20=20=20=20-=20seems=20AVM=20read=20is=20not=20checked?= =?UTF-8?q?=20upon=20"lookup"=20for=20the=20last=20node=20in=20the=20PATH?= =?UTF-8?q?=20(getType=20should=20have=20failed=20too)=20=20=20=2020466:?= =?UTF-8?q?=20Merged=20V2.2=20to=20V3.3=20=20=20=20=20=20=2020243:=20(RECO?= =?UTF-8?q?RD=20ONLY)=20ALF-2814:=20Merged=20V3.2=20to=20V2.2=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2017891:=20Merged=20DEV=5FTEMPORARY=20to=20V3.2?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=2017873:=20ETHREEOH-3810:?= =?UTF-8?q?=20WCM=20-=20Recursion=20detector=20erroring=20=20=20=2020467:?= =?UTF-8?q?=20Merged=20V3.1=20to=20V3.3=20(RECORD=20ONLY)=20=20=20=20=20?= =?UTF-8?q?=20=2020276:=20Incremented=20version=20label=20=20=20=20=20=20?= =?UTF-8?q?=2020275:=20ALF-2845:=20Merged=20V3.2=20to=20V3.1=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2017768:=20Merged=20DEV/BELARUS/V3.2-2009=5F11?= =?UTF-8?q?=5F24=20to=20V3.2=20=20=20=20=20=20=20=20=20=20=20=20=2017758:?= =?UTF-8?q?=20ETHREEOH-3757:=20Oracle=20upgrade=20issue:=20failed=20"invit?= =?UTF-8?q?eEmailTemplate"=20patch=20-=20also=20causes=20subsequent=20patc?= =?UTF-8?q?hes=20to=20not=20be=20applied=20=20=20=20=20=20=2019573:=20Merg?= =?UTF-8?q?ed=20V3.2=20to=20V3.1=20=20=20=20=20=20=20=20=20=2019539:=20Mer?= =?UTF-8?q?ged=20HEAD=20to=20V3.2=20=20=20=20=20=20=20=20=20=20=20=20=2019?= =?UTF-8?q?538:=20Build=20fix=20-=20fix=20build=20speed=20=20=20=2020468:?= =?UTF-8?q?=20Merged=20PATCHES/V3.2.r=20to=20V3.3=20(RECORD=20ONLY)=20=20?= =?UTF-8?q?=20=20=20=20=2020357:=20Merged=20PATCHES/V3.2.0=20to=20PATCHES/?= =?UTF-8?q?V3.2.r=20=20=20=20=20=20=20=20=20=2020349:=20Merged=20V3.3=20to?= =?UTF-8?q?=20PATCHES/V3.2.0=20=20=20=20=20=20=20=20=20=20=20=20=2020346:?= =?UTF-8?q?=20ALF-2839:=20Node=20pre-loading=20generates=20needless=20resu?= =?UTF-8?q?ltset=20rows=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20-?= =?UTF-8?q?=20Added=20missing=20Criteria.list()=20call=20=20=20=20=20=20?= =?UTF-8?q?=2020339:=20Incremented=20version=20label=20=20=20=20=20=20=202?= =?UTF-8?q?0338:=20Merged=20PATCHES/V3.2.0=20to=20PATCHES/V3.2.r=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2020280:=20Fixed=20ALF-2839:=20Node=20pre-lo?= =?UTF-8?q?ading=20generates=20needless=20resultset=20rows=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20-=20Split=20Criteria=20query=20to=20ret?= =?UTF-8?q?rieve=20properties=20and=20aspects=20separately=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=2020272:=20Backports=20to=20help=20fix=20ALF-2839:?= =?UTF-8?q?=20Node=20pre-loading=20generates=20needless=20resultset=20rows?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20Merged=20BRANCHES/V3.2?= =?UTF-8?q?=20to=20PATCHES/V3.2.0:=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=2018490:=20Added=20cache=20for=20alf=5Fcontent=5Fdata=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20Merged=20BRANCHES/DEV/V3.3-?= =?UTF-8?q?BUG-FIX=20to=20PATCHES/V3.2.0:=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=2020231:=20Fixed=20ALF-2784:=20Degradation=20of=20?= =?UTF-8?q?performance=20between=203.1.1=20and=203.2x=20(observed=20in=20J?= =?UTF-8?q?SF)=20=20=20=20=20=20=20=20=20=2020266:=20Test=20reproduction?= =?UTF-8?q?=20of=20ALF-2839=20failure:=20Node=20pre-loading=20generates=20?= =?UTF-8?q?needless=20resultset=20rows=20=20=20=2020469:=20Merged=20PATCHE?= =?UTF-8?q?S/V3.1.2=20to=20V3.3=20(RECORD=20ONLY)=20=20=20=20=20=20=202039?= =?UTF-8?q?3:=20Eclipse=20classpath=20fix=20to=20avoid=20problems=20in=20J?= =?UTF-8?q?Boss=20=20=20=20=20=20=2020309:=20ALF-2777:=20PrimaryChildAssoc?= =?UTF-8?q?CopyBehaviour=20from=20MOB-388=20corrupts=20cm:name=20attribute?= =?UTF-8?q?s=20of=20copied=20child=20nodes=20=20=20=20=20=20=20=20=20=20-?= =?UTF-8?q?=20Folded=20example=20behaviours=20from=20previous=20AMP=20into?= =?UTF-8?q?=20repository=20=20=20=20=20=20=20=20=20=20-=20Fixed=20PrimaryC?= =?UTF-8?q?hildAssocCopyBehaviour=20to=20back-up=20and=20set=20the=20cm:na?= =?UTF-8?q?me=20property=20on=20copied=20children=20=20=20=2020470:=20Merg?= =?UTF-8?q?ed=20PATCHES/V3.2.0=20to=20V3.3=20(RECORD=20ONLY)=20=20=20=20?= =?UTF-8?q?=20=20=2020465:=20Incremented=20version=20label=20=20=20=20=20?= =?UTF-8?q?=20=2020464:=20ALF-3060:=20Merged=20V3.2=20to=20PATCHES/V3.2.0?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=2019920:=20Merged=20HEAD=20to=20BRA?= =?UTF-8?q?NCHES/V3.2:=20=20=20=20=20=20=20=20=20=20=20=20=2019918:=20Fix?= =?UTF-8?q?=20ALF-2499=20(Deleting=20a=20web=20project=20also=20deletes=20?= =?UTF-8?q?similarly=20named=20web=20projects=20-=20Potential=20Data=20Los?= =?UTF-8?q?s)=20=20=20=20=20=20=2020448:=20Merged=20DEV/V3.3-BUG-FIX=20to?= =?UTF-8?q?=20PATCHES/V3.2.0=20=20=20=20=20=20=20=20=20=2020414:=20Merged?= =?UTF-8?q?=20DEV/BELARUS/HEAD-2010=5F04=5F28=20to=20DEV/V3.3-BUG-FIX=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=2020401:=20ALF-2616:=20Seriou?= =?UTF-8?q?s=20Web=20Form=20layout=20performance=20issues=20on=20IE8.=20Th?= =?UTF-8?q?is=20fix=20contains:=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20a)=20X-UA-Compatible=20head=20tag=20with=20IE=3DEmulateIE?= =?UTF-8?q?7=20value=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20b)=20a?= =?UTF-8?q?lfresco.ieVersion=20and=20alfresco.ieEngine=20in=20common.js=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20c)=20recurseOnChil?= =?UTF-8?q?dren=20in=20=5FupdateDisplay=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20d)=20Some=20performance=20modifications=20in=20x?= =?UTF-8?q?forms.js=20=20=20=20=20=20=2020350:=20Increment=20version=20lab?= =?UTF-8?q?el=20=20=20=20=20=20=2020349:=20Merged=20V3.3=20to=20PATCHES/V3?= =?UTF-8?q?.2.0=20=20=20=20=20=20=20=20=20=2020346:=20ALF-2839:=20Node=20p?= =?UTF-8?q?re-loading=20generates=20needless=20resultset=20rows=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20-=20Added=20missing=20Criteria.li?= =?UTF-8?q?st()=20call=20=20=20=2020471:=20Fix=20for=20offline=20sync=20lo?= =?UTF-8?q?sing=20metadata=20properties,=20due=20to=20rename/delete=20of?= =?UTF-8?q?=20original=20file.=20ALF-575.=20=20=20=2020478:=20Merged=20HEA?= =?UTF-8?q?D=20to=20BRANCHES/V3.3:=20(RECORD=20ONLY)=20=20=20=20=20=20=202?= =?UTF-8?q?0477:=20Fix=20ALF-3086:=20CMIS=20checkin=20of=20a=20non-version?= =?UTF-8?q?able=20document=20should=20make=20it=20versionable=20=20=20=202?= =?UTF-8?q?0479:=20ALF-2110:=20Make=20it=20possible=20to=20determine=20whi?= =?UTF-8?q?ch=20person=20properties=20are=20synced=20via=20LDAP=20and=20he?= =?UTF-8?q?nce=20immutable=20=20=20=20=20=20=20-=20Added=20Set=20Us?= =?UTF-8?q?erRegistrySynchronizer.getPersonMappedProperties(String=20usern?= =?UTF-8?q?ame)=20=20=20=20=20=20=20-=20UI/Services=20Fix=20to=20follow=20?= =?UTF-8?q?=20=20=2020481:=20Latest=20SpringSurf=20libs:=20=20=20=20=20=20?= =?UTF-8?q?=20-=20Fix=20for=20ALF-1518=20-=20Added=20support=20for=20HTTP?= =?UTF-8?q?=20and=20HTTPS=20proxies=20for=20Surf=20application=20remote=20?= =?UTF-8?q?api=20calls=20-=20via=20the=20standard=20JVM=20cmd=20line=20pro?= =?UTF-8?q?perties=20such=20as=20-Dhttp.proxyHost=3D...=20=20=20=2020484:?= =?UTF-8?q?=20ALF-2886:=20LDAP=20sync=20defaults=20display=20names=20incor?= =?UTF-8?q?rectly=20and=20can't=20cope=20with=20DNs=20containing=20escaped?= =?UTF-8?q?=20trailing=20whitespace.=20=20=20=20=20=20=20-=20Had=20to=20wo?= =?UTF-8?q?rk=20around=20a=20JDK=20bug=20in=20LDAP=20RDN=20parsing=20=20?= =?UTF-8?q?=20=2020486:=20Added=20case=20sensitive=20flag=20to=20the=20fil?= =?UTF-8?q?e=20state=20cache.=20Part=20of=20ALF-570.=20=20=20=2020487:=20F?= =?UTF-8?q?ix=20for=20copy/rename=20of=20folders=20causing=20file=20exists?= =?UTF-8?q?=20errors=20in=20some=20cases.=20ALF-570.=20=20=20=2020488:=20F?= =?UTF-8?q?ix=20ALF-680:=20Previously=20valid=20content=20models=20now=20f?= =?UTF-8?q?ail=20with=20CMISAbstractDictionaryService$DictionaryRegistry?= =?UTF-8?q?=20exception=20=20=20=2020489:=20Uploaded=20correct=20version?= =?UTF-8?q?=20of=20spring=20source=20jars=20and=20reunited=20them=20with?= =?UTF-8?q?=203rd-party/.classpath=20=20=20=2020490:=20Fix=20for=20cannot?= =?UTF-8?q?=20delete=20folders=20via=20CIFS=20from=20Mac=20OSX,=20due=20to?= =?UTF-8?q?=20desktop=20actions.=20ALF-2553.=20=20=20=2020491:=20Merged=20?= =?UTF-8?q?DEV/TEMPORARY=20to=20V3.3=20(With=20corrections)=20=20=20=20=20?= =?UTF-8?q?=20=2020485:=20ALF-2290:=20a=20HTTP=20GET=20request=20of=20a=20?= =?UTF-8?q?document=20redirects=20to=20the=20Home=20Location=20when=20usin?= =?UTF-8?q?g=20NTLM=20SSO=20=20=20=20=20=20=20=20=20=20The=20logic=20relat?= =?UTF-8?q?ed=20to=20ADB-61=20in=20NTLMAuthenticationFilter=20clears=20pre?= =?UTF-8?q?vious=20location=20and=20redirects=20request=20to=20default=20h?= =?UTF-8?q?ome=20location.=20NTLMAuthenticationFilter=20was=20changed=20to?= =?UTF-8?q?=20process=20GET=20requests=20to=20documents=20correctly.=20Now?= =?UTF-8?q?,=20fix=20to=20ADB-61=20processes=20only=20=E2=80=9C/faces?= =?UTF-8?q?=E2=80=9D=20requests=20and=20GET=20requests=20to=20documents=20?= =?UTF-8?q?are=20processed=20correctly.=20=20=20=20=20=20=20=20=20=20The?= =?UTF-8?q?=20same=20fix=20was=20made=20to=20KerberosAuthenticationFilter?= =?UTF-8?q?=20=20=20=2020492:=20Fix=20ALF-680:=20Previously=20valid=20cont?= =?UTF-8?q?ent=20models=20now=20fail=20with=20CMISAbstractDictionaryServic?= =?UTF-8?q?e$DictionaryRegistry=20exception=20=20=20=20=20=20=20-=20missin?= =?UTF-8?q?g=20remove=20directory=20=20=20=2020493:=20Fix=20ALF-2837:=20Cl?= =?UTF-8?q?assCastException=20in=20getProperties()=20=20=20=2020498:=20Fix?= =?UTF-8?q?=20for=20ALF-2818:=20Failure=20to=20close=20index=20writer=20un?= =?UTF-8?q?der=20certain=20conditions.=20=20=20=20=20=20=20-=20fix=20for?= =?UTF-8?q?=20index=20writer=20to=20close=20indexes=20when=20stopped=20by?= =?UTF-8?q?=20exceptions=20during=20FTS=20=20=20=20=20=20=20-=20fix=20FTS?= =?UTF-8?q?=20job=20to=20handle=20exceptions=20better=20=20=20=20=20=20=20?= =?UTF-8?q?-=20debug=20for=20FTS=20background=20operations=20=20=20=202049?= =?UTF-8?q?9:=20ALF-3094:=20In=20ticket=20authenticate=20method=20in=20Aut?= =?UTF-8?q?henticationHelper,=20invalidate=20the=20current=20session=20if?= =?UTF-8?q?=20its=20cached=20ticket=20doesn't=20match=20=20=20=2020500:=20?= =?UTF-8?q?Fix=20for=20ALF-2858=20"Zero=20KB=20sized=20bin=20files=20will?= =?UTF-8?q?=20be=20created=20in=20the=20contentstore=20when=20new=20sites?= =?UTF-8?q?=20are=20created"=20(RECORD=20ONLY)=20=20=20=2020503:=20AVMTest?= =?UTF-8?q?Suite:=20minor=20fixes=20to=20cleanup=20ctx=20usage=20(avoid=20?= =?UTF-8?q?re-loading)=20=20=20=2020505:=20Merged=20BRANCHES/V2.2=20to=20B?= =?UTF-8?q?RANCHES/V3.3=20(record-only)=20=20=20=20=20=20=2013859:=20(reco?= =?UTF-8?q?rd-only)=20Removed=20dev=20from=20version=20label=20=20=20=20?= =?UTF-8?q?=20=20=2014003:=20(record-only)=20Updated=20version=20to=202.2.?= =?UTF-8?q?5dev=20=20=20=20=20=20=2014566:=20(record-only)=20ETWOTWO-1239?= =?UTF-8?q?=20-=20remove=20workflow=20interpreter/console=20bootstrap=20?= =?UTF-8?q?=20=20=20=20=20=2014572:=20(record-only)=20ETWOTWO-1239=20-=20f?= =?UTF-8?q?ix=20PersonTest=20to=20fix=20JBPMEngineTest=20(part-sourced=20f?= =?UTF-8?q?rom=20r13247)=20=20=20=20=20=20=2014776:=20(record-only)=20Merg?= =?UTF-8?q?ed=20V3.1=20to=20V2.2=20=20=20=20=20=20=20=20=20=2014748:=20ETH?= =?UTF-8?q?REEOH-2225=20-=20WCM=20upgrade=20(performance=20improvements=20?= =?UTF-8?q?for=20MySQL)=20=20=20=2020506:=20NFS=20ReadDir/ReadDirPlus=20sk?= =?UTF-8?q?ips=20some=20folder=20entries.=20JLAN-98.=20=20=20=2020507:=20F?= =?UTF-8?q?ixed=20issue=20with=20folder=20search=20resume=20id=20being=20r?= =?UTF-8?q?eset=20to=20the=20wrong=20value=20during=20NFS=20folder=20searc?= =?UTF-8?q?h.=20Part=20of=20JLAN-98.=20=20=20=2020508:=20Merged=20BRANCHES?= =?UTF-8?q?/V3.2=20to=20BRANCHES/V3.3:=20=20=20=20=20=20=2018319:=20Merged?= =?UTF-8?q?=20BRANCHES/DEV/BELARUS/V3.2-2010=5F01=5F11=20to=20V3.2=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=2018273:=20ETHREEOH-3834:=20WCM:=20An=20e?= =?UTF-8?q?xtral=20.xml.html=20file=20is=20created=20when=20editing=20newl?= =?UTF-8?q?y=20created=20content=20=20=20=20=20=20=2019182:=20Merged=20V3.?= =?UTF-8?q?1=20to=20V3.2=20=20=20=20=20=20=20=20=20=2018423:=20ETHREEOH-38?= =?UTF-8?q?50=20-=20Content=20Manager=20unable=20to=20edit=20content=20ite?= =?UTF-8?q?ms=20if=20there=20is=20a=20lock=20on=20a=20generated=20renditio?= =?UTF-8?q?n=20=20=20=20=20=20=20=20=20=2018432:=20(RECORD=20ONLY)=20Added?= =?UTF-8?q?=20FTP=20data=20port=20range=20configuration=20via=20n:n=20config=20value.=20ETHREEOH-4103.=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2018451:=20(RECORD=20ONLY)=20Fixed=20incorrect?= =?UTF-8?q?=20FTP=20debug=20level=20name.=20=20=20=20=20=20=20=20=20=20185?= =?UTF-8?q?77:=20(RECORD=20ONLY)=20Fix=20for=20ETHREEOH-4117,=20based=20on?= =?UTF-8?q?=20CHK-11154=20=20=20=20=20=20=20=20=20=2018792:=20Fix=20ETHREE?= =?UTF-8?q?OH-2729:=20=20Import=20of=20property=20with=20@=20symbol=20in?= =?UTF-8?q?=20name=20fails=20with=20"start=20tag=20unexpected=20character?= =?UTF-8?q?=20@=20"=20=20=20=20=20=20=2019570:=20ALF-192=20/=20ALF-1750:?= =?UTF-8?q?=20=20System=20Error=20if=20user=20trying=20submit=20web=20cont?= =?UTF-8?q?ent=20based=20on=20web=20form=20which=20was=20deleted=20=20=20?= =?UTF-8?q?=20=20=20=2019583:=20Merged=20DEV/BELARUS/V3.2-2010=5F03=5F17?= =?UTF-8?q?=20to=20V3.2=20=20=20=20=20=20=20=20=20=2019545:=20ALF-1954:=20?= =?UTF-8?q?Regression:=20same=20item=20can=20be=20submitted=20multiple=20t?= =?UTF-8?q?imes=20to=20workflow=20=20=20=20=20=20=2019725:=20AVMStoreDescr?= =?UTF-8?q?iptor=20-=20fix=20minor=20typo=20(for=20debugging)=20=20=20=20?= =?UTF-8?q?=20=20=2019917:=20(RECORD=20ONLY)=20Merged=20HEAD=20to=20BRANCH?= =?UTF-8?q?ES/V3.2:=20=20=20=20=20=20=20=20=20=2019880:=20Fix=20ALF-898=20?= =?UTF-8?q?-=20WCM:=20Deleting=20a=20file=20leads=20to=20error=20(only=20i?= =?UTF-8?q?f=20RM/DOD=20installed)=20=20=20=20=20=20=2019920:=20(RECORD=20?= =?UTF-8?q?ONLY)=20Merged=20HEAD=20to=20BRANCHES/V3.2:=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2019918:=20Fix=20ALF-2499=20(Deleting=20a=20web=20pro?= =?UTF-8?q?ject=20also=20deletes=20similarly=20named=20web=20projects=20-?= =?UTF-8?q?=20Potential=20Data=20Loss)=20=20=20=2020509:=20Merged=20BRANCH?= =?UTF-8?q?ES/V3.2=20to=20BRANCHES/V3.3=20(RECORD=20ONLY):=20=20=20=20=20?= =?UTF-8?q?=20=2019825:=20(RECORD=20ONLY)=20Merged=20PATCHES/V3.2.r=20to?= =?UTF-8?q?=20BRANCHES/V3.2:=20=20=20=20=20=20=20=20=20=2019804:=20Merged?= =?UTF-8?q?=20PATCHES/V3.2.0=20to=20PATCHES/V3.2.r=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20Merged=20HEAD=20to=20V3.2.0=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20...=20=20=20=2020510:=20Merged=20BRANCHES/V3.?= =?UTF-8?q?1=20to=20BRANCHES/V3.3=20(RECORD=20ONLY)=20=20=20=20=20=20=2017?= =?UTF-8?q?482:=20(RECORD=20ONLY)=20Merged=20V3.2=20to=20V3.1=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2017478:=20Fix=20ETHREEOH-3340=20-=20WCM=20-=20?= =?UTF-8?q?Revert=20to=20snapshot=20failure=20(fix=20AVM=20getListing=20->?= =?UTF-8?q?=20AVMSync=20compare=20->=20WCM=20revertSnapshot)=20=20=20=20?= =?UTF-8?q?=20=20=2018783:=20(RECORD=20ONLY)=20MT:=20ensure=20group=20(EMA?= =?UTF-8?q?IL=5FCONTRIBUTORS)=20bootstraps=20tenant=20admin=20user=20(when?= =?UTF-8?q?=20creating=20tenant)=20=20=20=2020513:=20Added=20port=20change?= =?UTF-8?q?=20example=20for=20remote=20Alfresco=20server=20to=20share-conf?= =?UTF-8?q?ig-custom.xml.sample=20=20=20=2020518:=20ALF-657=20Created=20te?= =?UTF-8?q?sts=20to=20check=20that=20the=20'runas'=20functionality=20works?= =?UTF-8?q?=20in=20the=20AlfrescoJavaScript=20action=20handler.=20Also=20m?= =?UTF-8?q?odified=20the=20handler=20to=20run=20as=20the=20System=20user?= =?UTF-8?q?=20if=20no=20Authentication=20is=20currently=20set,=20as=20may?= =?UTF-8?q?=20occur=20if=20the=20action=20handler=20is=20being=20executed?= =?UTF-8?q?=20asynchronously.=20=20=20=2020519:=20ALF-657=20Created=20test?= =?UTF-8?q?s=20to=20check=20that=20the=20'runas'=20functionality=20works?= =?UTF-8?q?=20in=20the=20AlfrescoJavaScript=20action=20handler.=20Also=20m?= =?UTF-8?q?odified=20the=20handler=20to=20run=20as=20the=20System=20user?= =?UTF-8?q?=20if=20no=20Authentication=20is=20currently=20set,=20as=20may?= =?UTF-8?q?=20occur=20if=20the=20action=20handler=20is=20being=20executed?= =?UTF-8?q?=20asynchronously.=20=20=20=2020520:=20Removed=20dev=20version.?= =?UTF-8?q?label=20=20=20=2020522:=20ALF-3129:=20Map=20cm:organization=20p?= =?UTF-8?q?roperty=20in=20LDAP=20as=20well=20as=20cm:organizationId,=20sin?= =?UTF-8?q?ce=20cm:organization=20is=20what=20shows=20up=20in=20JSF=20and?= =?UTF-8?q?=20Share.=20Needed=20by=20ALF-2110.=20=20=20=2020523:=20First?= =?UTF-8?q?=20part=20of=20fix=20for=20ALF-2110:=20=20=20=20=20=20=20-=20Ap?= =?UTF-8?q?propriate=20Person=20and=20webframework=20metadata=20APIs=20now?= =?UTF-8?q?=20return=20information=20on=20immutability=20of=20Person=20pro?= =?UTF-8?q?perties=20(as=20some=20properties=20are=20immutable=20when=20sy?= =?UTF-8?q?nced=20to=20LDAP=20etc.)=20=20=20=20=20=20=20-=20Share=20client?= =?UTF-8?q?=20=20now=20correctly=20disables=20profile=20fields=20in=20User?= =?UTF-8?q?=20Profile=20and=20Admin=20User=20Console=20as=20appropriate=20?= =?UTF-8?q?based=20on=20individual=20user=20property=20mutability=20=20=20?= =?UTF-8?q?=20=20=20=20-=20Change=20Password=20button=20now=20correctly=20?= =?UTF-8?q?enabled/disabled=20based=20on=20account=20mutability=20=20=20?= =?UTF-8?q?=2020524:=20VersionMigrator=20-=20option=20to=20run=20as=20sche?= =?UTF-8?q?duled=20job=20(ALF-1000)=20=20=20=2020525:=20Fix=20for=20variou?= =?UTF-8?q?s=20IE6=20CSS=20issues:=20=20=20=20=20=20=20ALF-3047=20-=20It's?= =?UTF-8?q?=20impossible=20to=20destinate=20any=20action=20with=20data=20l?= =?UTF-8?q?ist=20item=20(IE6=20specific)=20=20=20=20=20=20=20ALF-3049=20-?= =?UTF-8?q?=20Incorrect=20layout=20of=20Manage=20aspects=20page=20=20=20?= =?UTF-8?q?=20=20=20=20ALF-3050=20-=20Incorrect=20layout=20of=20Assign=20W?= =?UTF-8?q?orkflow=20form=20=20=20=2020526:=20Fix=20for=20ALF-2915=20-=20S?= =?UTF-8?q?elect=20>=20None=20feature=20for=20Data=20Lists=20not=20working?= =?UTF-8?q?=20across=20multiple=20pages=20in=20IE=20=20=20=20=20=20=20Clos?= =?UTF-8?q?ed=20ALF-2846=20-=20DataList=20UI=20not=20fully=20I18Ned=20[Old?= =?UTF-8?q?=20prototype=20code]=20=20=20=2020527:=20Fix=20for=20ALF-3082?= =?UTF-8?q?=20-=20There=20is=20no=20Edit=20Offline=20action=20at=20Details?= =?UTF-8?q?=20page=20in=20Share=20site=20=20=20=2020528:=20Fix=20various?= =?UTF-8?q?=20script=20errors=20due=20to=20typo:=20=20=20=20=20=20=20ALF-3?= =?UTF-8?q?088=20-=20Script=20error=20occurs=20on=20creating=20duplicated?= =?UTF-8?q?=20record=20seria=20=20=20=20=20=20=20ALF-3012=20-=20Incorrect?= =?UTF-8?q?=20behaviour=20on=20creating=20duplicating=20folders=20=20=20?= =?UTF-8?q?=20=20=20=20ALF-3004=20-=20Script=20error=20when=20submitting?= =?UTF-8?q?=20an=20item=20with=20long=20data=20in=20Prioprity=20field=20?= =?UTF-8?q?=20=20=2020529:=20Fix=20for=20ALF-3006=20-=20Selected=20Items?= =?UTF-8?q?=20>=20Copy=20to...=20and=20Move=20to=20actions=20not=20working?= =?UTF-8?q?=20in=20Document=20Library=20=20=20=2020530:=20Dynamic=20Models?= =?UTF-8?q?=20-=20fix=20test(s)=20=20=20=20=20=20=20-=20fix=20concurrency?= =?UTF-8?q?=20test=20for=20Oracle=20build=20(retry=20if=20txn=20lock=20can?= =?UTF-8?q?not=20be=20acquired)=20=20=20=20=20=20=20-=20when=20getting=20d?= =?UTF-8?q?eployed=20models,=20skip=20if=20invalid=20(eg.=20cannot=20be=20?= =?UTF-8?q?parsed)=20=20=20=2020536:=20Remove=20@Override=20(ALF-657)=20?= =?UTF-8?q?=20=20=2020537:=20Activities=20-=20(minor)=20fix=20NPE=20for=20?= =?UTF-8?q?Oracle=20build/test=20=20=20=2020543:=20Final=20part=20of=20ALF?= =?UTF-8?q?-2110=20-=20Appropriate=20person=20properties=20disabled=20for?= =?UTF-8?q?=20editing=20in=20Explorer=20Client=20if=20external=20mapped=20?= =?UTF-8?q?sync=20such=20as=20LDAP=20is=20used.=20=20=20=20=20=20=20Fixed?= =?UTF-8?q?=20issue=20with=20Change=20Password=20option=20being=20disabled?= =?UTF-8?q?=20incorrectly.=20=20=20=2020544:=20Follow-up=20fix=20to=20r205?= =?UTF-8?q?28=20=20=20=2020546:=20Fix=20for=20ALF-3151=20-=20Freemarker=20?= =?UTF-8?q?causes=20NPE=20while=20deploying=203.3=20enterprise=20onto=20We?= =?UTF-8?q?bSphere=207.0.0.7=20=20=20=20=20=20=20-=20NOTE:=20will=20need?= =?UTF-8?q?=20to=20submit=20patch=20to=20freemarker.org=20=20=20=2020552:?= =?UTF-8?q?=20Merged=20BRANCHES/V3.2=20to=20BRANCHES/V3.3=20(RECORD=20ONLY?= =?UTF-8?q?)=20=20=20=20=20=20=2020551:=20(RECORD=20ONLY)=20Merged=20BRANC?= =?UTF-8?q?HES/V3.3=20to=20BRANCHES/V3.2:=20=20=20=20=20=20=20=20=20=20200?= =?UTF-8?q?90:=20Dynamic=20models:=20minor=20improvements=20to=20Dictionar?= =?UTF-8?q?yModelType=20=20=20=2020553:=20Fix=20for=20escalated=20issue=20?= =?UTF-8?q?ALF-2856:=20Space=20returns=20to=20browse=20view=20after=20comp?= =?UTF-8?q?leting=20Add=20Content=20dialog;=20need=20a=20way=20to=20return?= =?UTF-8?q?=20to=20custom=20view=20(applied=20patch=20provided=20by=20cust?= =?UTF-8?q?omer).=20=20=20=2020554:=20Improvement=20to=20model=20delete=20?= =?UTF-8?q?validation=20(investigating=20intermittent=20failure=20of=20Rep?= =?UTF-8?q?oAdminServiceImplTest.testSimpleDynamicModelViaNodeService)=20?= =?UTF-8?q?=20=20=2020558:=20Merged=20DEV/BELARUS/V3.3-2010=5F06=5F08=20to?= =?UTF-8?q?=20V3.3=20=20=20=20=20=20=2020550:=20ALF-922:=20Mysql=20does=20?= =?UTF-8?q?not=20support=20unique=20keys=20that=20contain=20nulls=20as=20o?= =?UTF-8?q?ne=20would=20expect=20......=20duplicates=20in=20the=20alf=5Fac?= =?UTF-8?q?cess=5Fcontrol=5Fentry=20table=20=20=20=2020562:=20ALF-3177=20-?= =?UTF-8?q?=20security=20fix.=20=20=20=2020563:=20Merged=20BRANCHES/V3.2?= =?UTF-8?q?=20to=20BRANCHES/V3.3:=20=20=20=20=20=20=2019412:=20Fix=20for?= =?UTF-8?q?=20ALF-865=20"WCM=20/=20Cluster:=20unexpected=20error=20when=20?= =?UTF-8?q?concurrently=20submitting=20content"=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20ALF-862=20"WCM=20submit=20execution=20will=20require=20locki?= =?UTF-8?q?ng=20in=20a=20clustered=20WCM=20authoring=20env"=20=20=20=20205?= =?UTF-8?q?64:=20Merged=20BRANCHES/V3.1=20to=20BRANCHES/V3.3:=20=20=20=20?= =?UTF-8?q?=20=20=2020542:=20Fixed=20ALF-3152:=20ImporterComponent=20trans?= =?UTF-8?q?action=20retry=20settings=20can=20cause=20IllegalArgumentExcept?= =?UTF-8?q?ion=20=20=20=2020568:=20Follow-up=20on=20fix=20ALF-3152.=20=20F?= =?UTF-8?q?ix=20jobLockService's=20retryWaitIncrementMs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@20572 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/bootstrap-context.xml | 1 + config/alfresco/core-services-context.xml | 18 +- .../modify-index-permission_id.sql | 77 +++++ .../modify-index-permission_id.sql | 77 +++++ .../alfresco/domain/hibernate-cfg.properties | 2 +- config/alfresco/domain/transaction.properties | 2 +- .../messages/patch-service.properties | 1 + .../messages/version-service.properties | 3 +- config/alfresco/model/contentModel.xml | 125 +++++++ .../deprecated/deprecated_contentModel.xml | 68 ---- config/alfresco/model/emailServerModel.xml | 27 ++ config/alfresco/model/renditionModel.xml | 94 ------ config/alfresco/network-protocol-context.xml | 1 + config/alfresco/node-services-context.xml | 1 + .../alfresco/patch/patch-services-context.xml | 22 +- config/alfresco/repository.properties | 14 +- config/alfresco/scheduled-jobs-context.xml | 29 ++ config/alfresco/script-services-context.xml | 1 + .../Authentication/common-ldap-context.xml | 6 + config/alfresco/version.properties | 8 +- .../alfresco/test-hibernate-cfg.properties | 2 +- .../filesys/repo/ContentDiskDriver.java | 77 ++++- .../filesys/repo/ContentQuotaManager.java | 32 +- .../filesys/repo/ContentSearchContext.java | 4 +- .../filesys/repo/LinkMemoryNetworkFile.java | 2 +- .../activities/post/lookup/PostLookup.java | 4 +- .../repo/admin/RepoAdminServiceImpl.java | 90 +++-- .../patch/impl/MigrateVersionStorePatch.java | 291 +++++++++++++++-- .../avm/AVMDeploymentAttemptCleanerTest.java | 4 +- .../repo/avm/AVMExpiredContentTest.java | 3 +- .../repo/avm/AVMServiceLocalTest.java | 24 +- .../repo/avm/AVMServicePermissionsTest.java | 5 +- .../alfresco/repo/avm/AVMServiceTestBase.java | 5 +- .../org/alfresco/repo/avm/AVMTestSuite.java | 10 +- .../repo/avm/AbstractSpringContextTest.java | 62 ---- .../repo/avm/WCMInheritPermissionsTest.java | 68 ++-- .../avm/locking/AVMLockingServiceTest.java | 4 +- .../repo/dictionary/DictionaryDAOImpl.java | 9 +- .../repo/dictionary/DictionaryModelType.java | 76 +++-- .../repo/dictionary/M2ClassDefinition.java | 12 + .../dictionary/M2ConstraintDefinition.java | 4 + .../repo/dictionary/M2DataTypeDefinition.java | 4 + .../repo/dictionary/M2ModelDefinition.java | 59 ++++ .../alfresco/repo/dictionary/TestModel.java | 1 + .../repo/domain/hibernate/Permission.hbm.xml | 8 +- .../repo/exporter/ViewXMLExporter.java | 7 +- .../repo/forms/FormServiceImplTest.java | 283 ++++++++++++++-- .../node/ContentModelFormProcessor.java | 12 + .../repo/importer/view/ViewParser.java | 5 +- .../org/alfresco/repo/jscript/People.java | 35 +- .../ml/tools/EditionServiceImplTest.java | 18 +- .../repo/rule/RuleServiceCoverageTest.java | 242 ++++++-------- .../alfresco/repo/rule/RuleServiceImpl.java | 28 ++ .../repo/rule/RuntimeRuleService.java | 45 +++ .../ruletrigger/CreateNodeRuleTrigger.java | 162 ++++----- .../impl/lucene/ADMLuceneIndexerImpl.java | 7 +- .../lucene/fts/FullTextSearchIndexerImpl.java | 80 ++++- .../permissions/impl/acegi/ACLEntryVoter.java | 26 +- .../security/person/PersonServiceImpl.java | 5 +- .../ChainingUserRegistrySynchronizer.java | 46 ++- .../ChainingUserRegistrySynchronizerTest.java | 12 + .../repo/security/sync/UserRegistry.java | 10 + .../sync/UserRegistrySynchronizer.java | 13 + .../security/sync/ldap/LDAPUserRegistry.java | 112 ++++++- .../RetryingTransactionHelper.java | 4 + .../transfer/HttpClientTransmitterImpl.java | 2 +- .../repo/version/MigrationCleanupJob.java | 33 ++ .../repo/version/Version2ServiceImpl.java | 83 ++++- .../repo/version/VersionMigrator.java | 280 ++++++++++------ .../repo/version/VersionMigratorTest.java | 2 +- .../version/common/VersionHistoryImpl.java | 2 +- .../common/VersionLabelComparator.java | 17 +- .../workflow/jbpm/AlfrescoJavaScript.java | 258 ++++++++++----- .../AlfrescoJavaScriptIntegrationTest.java | 184 +++++++++++ .../cmr/dictionary/ModelDefinition.java | 22 ++ .../service/cmr/version/VersionHistory.java | 2 +- .../alfresco/wcm/sandbox/script/Asset.java | 308 ++++++++++++++++-- .../wcm/webproject/script/WebProjects.java | 7 +- .../jbpm-test/test-hibernate-cfg.properties | 2 +- 79 files changed, 2849 insertions(+), 942 deletions(-) create mode 100644 config/alfresco/dbscripts/upgrade/3.3/org.hibernate.dialect.MySQLInnoDBDialect/modify-index-permission_id.sql create mode 100644 config/alfresco/dbscripts/upgrade/3.3/org.hibernate.dialect.PostgreSQLDialect/modify-index-permission_id.sql delete mode 100644 config/alfresco/model/deprecated/deprecated_contentModel.xml delete mode 100644 config/alfresco/model/renditionModel.xml delete mode 100644 source/java/org/alfresco/repo/avm/AbstractSpringContextTest.java create mode 100644 source/java/org/alfresco/repo/workflow/jbpm/AlfrescoJavaScriptIntegrationTest.java diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index be6b6d27da..d6f9e1d0e1 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -107,6 +107,7 @@ + diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 6d932ebd1a..ed41d5c6fc 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -901,6 +901,9 @@ ${version.store.onlyUseDeprecatedV1} + + + @@ -969,12 +972,12 @@ - - - + + + 500 @@ -1050,7 +1053,6 @@ alfresco/model/contentModel.xml - alfresco/model/renditionModel.xml alfresco/model/bpmModel.xml alfresco/model/wcmModel.xml alfresco/model/forumModel.xml @@ -1083,9 +1085,6 @@ alfresco/model/datalistModel.xml - - - alfresco/model/deprecated/deprecated_contentModel.xml @@ -1227,6 +1226,9 @@ + + ${fts.indexer.batchSize} + @@ -1416,7 +1418,7 @@ 10 - 0 + 1 diff --git a/config/alfresco/dbscripts/upgrade/3.3/org.hibernate.dialect.MySQLInnoDBDialect/modify-index-permission_id.sql b/config/alfresco/dbscripts/upgrade/3.3/org.hibernate.dialect.MySQLInnoDBDialect/modify-index-permission_id.sql new file mode 100644 index 0000000000..0a120d7112 --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/3.3/org.hibernate.dialect.MySQLInnoDBDialect/modify-index-permission_id.sql @@ -0,0 +1,77 @@ +-- +-- Title: Upgrade to V3.3 - Remove context_id from the permission_id index on alf_access_control_list_entry +-- Database: MySQL +-- Since: V3.3 schema 4011 +-- Author: andyh +-- +-- Remove context_id from the permission_id unique index (as it alwaays contains null and therefore has no effect) +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + + + +-- The remainder of this script is adapted from +-- Repository/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoSchemaUpdate-2.2-ACL.sql +-- Ports should do the same and reflect the DB specific improvements + +CREATE TABLE alf_tmp_min_ace ( + min BIGINT NOT NULL, + permission_id BIGINT NOT NULL, + authority_id BIGINT NOT NULL, + allowed BIT(1) NOT NULL, + applies INT NOT NULL, + UNIQUE (permission_id, authority_id, allowed, applies) +) ENGINE=InnoDB; + +INSERT INTO alf_tmp_min_ace (min, permission_id, authority_id, allowed, applies) + SELECT + min(ace1.id), + ace1.permission_id, + ace1.authority_id, + ace1.allowed, + ace1.applies + FROM + alf_access_control_entry ace1 + GROUP BY + ace1.permission_id, ace1.authority_id, ace1.allowed, ace1.applies +; + + +-- Update members to point to the first use of an access control entry +UPDATE alf_acl_member mem + SET ace_id = (SELECT help.min FROM alf_access_control_entry ace + JOIN alf_tmp_min_ace help + ON help.permission_id = ace.permission_id AND + help.authority_id = ace.authority_id AND + help.allowed = ace.allowed AND + help.applies = ace.applies + WHERE ace.id = mem.ace_id ); + +DROP TABLE alf_tmp_min_ace; + +-- Remove duplicate aces the mysql way (as you can not use the deleted table in the where clause ...) + +CREATE TABLE tmp_to_delete SELECT ace.id FROM alf_acl_member mem RIGHT OUTER JOIN alf_access_control_entry ace ON mem.ace_id = ace.id WHERE mem.ace_id IS NULL; +DELETE FROM ace USING alf_access_control_entry ace JOIN tmp_to_delete t ON ace.id = t.id; +DROP TABLE tmp_to_delete; + +-- Add constraint for duplicate acls (this no longer includes the context) + + +ALTER TABLE alf_access_control_entry DROP INDEX permission_id; +ALTER TABLE alf_access_control_entry + ADD UNIQUE permission_id (permission_id, authority_id, allowed, applies); + + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V3.3-modify-index-permission_id'; +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.3-modify-index-permission_id', 'Remove context_id from the permission_id unique index (as it always contains null and therefore has no effect)', + 0, 4102, -1, 4103, null, 'UNKOWN', 1, 1, 'Script completed' + ); diff --git a/config/alfresco/dbscripts/upgrade/3.3/org.hibernate.dialect.PostgreSQLDialect/modify-index-permission_id.sql b/config/alfresco/dbscripts/upgrade/3.3/org.hibernate.dialect.PostgreSQLDialect/modify-index-permission_id.sql new file mode 100644 index 0000000000..bb8e778e02 --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/3.3/org.hibernate.dialect.PostgreSQLDialect/modify-index-permission_id.sql @@ -0,0 +1,77 @@ +-- +-- Title: Upgrade to V3.3 - Remove context_id from the permission_id index on alf_access_control_list_entry +-- Database: PostgreSQL +-- Since: V3.3 schema 4011 +-- Author: +-- +-- Remove context_id from the permission_id unique index (as it alwaays contains null and therefore has no effect) +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + + + +-- The remainder of this script is adapted from +-- Repository/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.PostgreSQLDialect/AlfrescoSchemaUpdate-2.2-ACL.sql +-- Ports should do the same and reflect the DB specific improvements + +CREATE TABLE alf_tmp_min_ace ( + min INT8 NOT NULL, + permission_id INT8 NOT NULL, + authority_id INT8 NOT NULL, + allowed BOOL NOT NULL, + applies INT4 NOT NULL, + UNIQUE (permission_id, authority_id, allowed, applies) +); + +INSERT INTO alf_tmp_min_ace (min, permission_id, authority_id, allowed, applies) + SELECT + min(ace1.id), + ace1.permission_id, + ace1.authority_id, + ace1.allowed, + ace1.applies + FROM + alf_access_control_entry ace1 + GROUP BY + ace1.permission_id, ace1.authority_id, ace1.allowed, ace1.applies +; + + +-- Update members to point to the first use of an access control entry +UPDATE alf_acl_member mem + SET ace_id = (SELECT help.min FROM alf_access_control_entry ace + JOIN alf_tmp_min_ace help + ON help.permission_id = ace.permission_id AND + help.authority_id = ace.authority_id AND + help.allowed = ace.allowed AND + help.applies = ace.applies + WHERE ace.id = mem.ace_id ); + +DROP TABLE alf_tmp_min_ace; + +-- Remove duplicate aces the mysql way (as you can not use the deleted table in the where clause ...) + +CREATE TABLE tmp_to_delete AS SELECT ace.id FROM alf_acl_member mem RIGHT OUTER JOIN alf_access_control_entry ace ON mem.ace_id = ace.id WHERE mem.ace_id IS NULL; +DELETE FROM alf_access_control_entry ace USING tmp_to_delete t WHERE ace.id = t.id; +DROP TABLE tmp_to_delete; + +-- Add constraint for duplicate acls (this no longer includes the context) + + +ALTER TABLE alf_access_control_entry DROP CONSTRAINT alf_access_control_entry_permission_id_key; +ALTER TABLE alf_access_control_entry + ADD UNIQUE (permission_id, authority_id, allowed, applies); + + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V3.3-modify-index-permission_id'; +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.3-modify-index-permission_id', 'Remove context_id from the permission_id unique index (as it always contains null and therefore has no effect)', + 0, 4102, -1, 4103, null, 'UNKOWN', TRUE, TRUE, 'Script completed' + ); diff --git a/config/alfresco/domain/hibernate-cfg.properties b/config/alfresco/domain/hibernate-cfg.properties index 579a581208..5e7be7e961 100644 --- a/config/alfresco/domain/hibernate-cfg.properties +++ b/config/alfresco/domain/hibernate-cfg.properties @@ -9,7 +9,7 @@ hibernate.jdbc.use_streams_for_binary=true hibernate.show_sql=false -hibernate.cache.use_query_cache=true +hibernate.cache.use_query_cache=false hibernate.max_fetch_depth=10 hibernate.cache.provider_class=org.alfresco.repo.cache.InternalEhCacheManagerFactoryBean hibernate.cache.use_second_level_cache=true diff --git a/config/alfresco/domain/transaction.properties b/config/alfresco/domain/transaction.properties index daab922c71..0071eb5c2a 100644 --- a/config/alfresco/domain/transaction.properties +++ b/config/alfresco/domain/transaction.properties @@ -18,4 +18,4 @@ server.transaction.wait-increment-ms=100 server.setup.transaction.max-retries=40 server.setup.transaction.min-retry-wait-ms=15000 server.setup.transaction.max-retry-wait-ms=15000 -server.setup.transaction.wait-increment-ms=0 \ No newline at end of file +server.setup.transaction.wait-increment-ms=10 \ No newline at end of file diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties index bcdf7f4660..2cba712eb7 100644 --- a/config/alfresco/messages/patch-service.properties +++ b/config/alfresco/messages/patch-service.properties @@ -252,6 +252,7 @@ patch.sitePermissionRefactorPatch.result=Groups have been created for all sites patch.migrateVersionStore.description=Version Store migration (from lightWeightVersionStore to version2Store) patch.migrateVersionStore.incomplete=Version Store migration incomplete. patch.migrateVersionStore.done=Version Store migration completed. +patch.migrateVersionStore.bypassingPatch=Bypass Version Store migration patch since scheduled to run as job patch.inviteEmailTemplate.description=Adds invite email template to invite space diff --git a/config/alfresco/messages/version-service.properties b/config/alfresco/messages/version-service.properties index 26f8bd441b..97ea432fc7 100644 --- a/config/alfresco/messages/version-service.properties +++ b/config/alfresco/messages/version-service.properties @@ -10,7 +10,8 @@ version_service.err_revert_mismatch=The version provided to revert to does not c version_service.migration.patch.noop=Nothing to do (no version histories found in old version store) -version_service.migration.patch.complete=Completed migration of {0} (out of {1}) of old version histories (to new version store) in {2} secs +version_service.migration.patch.complete=Migration completed - migrated {0} (out of {1}) of old version histories (to new version store) in {2} secs (deleteImmediately={3}) +version_service.migration.patch.in_progress=Migration in progress - migrated {0} (out of {1}) of old version histories (to new version store) in {2} secs (deleteImmediately={3}) version_service.migration.patch.warn.skip1=Skipped migration of {0} (out of {1}) batches of old version histories (migrate incomplete) in {2} secs version_service.migration.patch.warn.skip2=Skipped migration of {0} old version histories (already migrated) diff --git a/config/alfresco/model/contentModel.xml b/config/alfresco/model/contentModel.xml index 4074598076..73b52b1809 100644 --- a/config/alfresco/model/contentModel.xml +++ b/config/alfresco/model/contentModel.xml @@ -16,6 +16,7 @@ + @@ -421,6 +422,34 @@ + + + + + + + Thumbnail + cm:content + true + false + + + Thumbnail Name + d:text + false + + + Thumbnailed Content Property Name + d:qname + true + + + + + + + + @@ -1050,6 +1079,102 @@ + + + + + + + + + Rendition + + + + Hidden Rendition + rn:rendition + + + + Visible Rendition + rn:rendition + + + + + Renditioned + + + + + false + true + + + cm:content + false + true + + + + + + + + + + + + + Localizable + + + Locale + d:category + + + + + + Translatable + cm:localizable + + + Translations + + cm:translationOf + false + false + + + cm:content + cm:hasTranslation + false + true + + + + + + + + Thumbnailed + rn:renditioned + + + Automatic Update + d:boolean + true + true + + + + + + + + diff --git a/config/alfresco/model/deprecated/deprecated_contentModel.xml b/config/alfresco/model/deprecated/deprecated_contentModel.xml deleted file mode 100644 index df8e54b869..0000000000 --- a/config/alfresco/model/deprecated/deprecated_contentModel.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - Deprecated Content Domain Model Types and Aspects - Alfresco - 2006-12-12 - 1.0 - - - - - - - - - - - - Localizable - - - Locale - d:category - - - - - - Translatable - cm:localizable - - - Translations - - cm:translationOf - false - false - - - cm:content - cm:hasTranslation - false - true - - - - - - - Attached - - - Attachment - - true - true - - - cm:content - false - false - - - - - - - - diff --git a/config/alfresco/model/emailServerModel.xml b/config/alfresco/model/emailServerModel.xml index eaa14dc4ff..ffacf7c50f 100644 --- a/config/alfresco/model/emailServerModel.xml +++ b/config/alfresco/model/emailServerModel.xml @@ -53,5 +53,32 @@ + + + + + + + Attached + + + Attachment + + true + true + + + cm:content + false + false + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/model/renditionModel.xml b/config/alfresco/model/renditionModel.xml deleted file mode 100644 index f99c1fb059..0000000000 --- a/config/alfresco/model/renditionModel.xml +++ /dev/null @@ -1,94 +0,0 @@ - - - - Alfresco Rendition Model - Alfresco - 2010-01-14 - 1.0 - - - - - - - - - - - - - - Thumbnail - cm:content - true - false - - - Thumbnail Name - d:text - false - - - Thumbnailed Content Property Name - d:qname - true - - - - - - - - - Rendition - - - - Hidden Rendition - rn:rendition - - - - Visible Rendition - rn:rendition - - - - - Renditioned - - - - - false - true - - - cm:content - false - true - - - - - - - - - - Thumbnailed - rn:renditioned - - - Automatic Update - d:boolean - true - true - - - - - - - diff --git a/config/alfresco/network-protocol-context.xml b/config/alfresco/network-protocol-context.xml index cc5fc764ef..c49a630854 100644 --- a/config/alfresco/network-protocol-context.xml +++ b/config/alfresco/network-protocol-context.xml @@ -66,6 +66,7 @@ + diff --git a/config/alfresco/node-services-context.xml b/config/alfresco/node-services-context.xml index 898becd8fe..36c149166d 100644 --- a/config/alfresco/node-services-context.xml +++ b/config/alfresco/node-services-context.xml @@ -242,6 +242,7 @@ ${spaces.archive.store} + ${version.store.version2Store} diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 9498a9ec58..4f3b6ea077 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -1373,12 +1373,22 @@ + + + ${version.store.migrateVersionStore.threadCount} ${version.store.migrateVersionStore.batchSize} + + ${version.store.migrateVersionStore.runAsScheduledJob} + + + ${version.store.migrateVersionStore.limitPerJobCycle} + + @@ -2197,7 +2207,7 @@ - + patch.updateMimetypes1 patch.updateMimetypes1.description @@ -2233,4 +2243,14 @@ + + patch.db-V3.3-modify-index-permission_id + patch.schemaUpgradeScript.description + 0 + 4102 + 4103 + + classpath:alfresco/dbscripts/upgrade/3.3/${db.script.dialect}/modify-index-permission_id.sql + + \ No newline at end of file diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index e7a65fbe9a..39909c0ed6 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -41,7 +41,7 @@ index.recovery.maximumPoolSize=5 # Never: * * * * * ? 2099 # Once every five seconds: 0/5 * * * * ? # Once every two seconds : 0/2 * * * * ? -# See http://quartz.sourceforge.net/javadoc/org/quartz/CronTrigger.html +# See http://www.quartz-scheduler.org/docs/tutorials/crontrigger.html index.tracking.cronExpression=0/5 * * * * ? index.tracking.adm.cronExpression=${index.tracking.cronExpression} index.tracking.avm.cronExpression=${index.tracking.cronExpression} @@ -173,6 +173,7 @@ lucene.query.maxClauses=10000 # http://issues.alfresco.com/browse/AR-1280: Setting this high is the workaround as of 1.4.3. # lucene.indexer.batchSize=1000000 +fts.indexer.batchSize=1000 # # Index cache sizes # @@ -334,6 +335,17 @@ version.store.migrateCleanupJob.batchSize=1 # WARNING: For non-production testing only !!! Do not change (to avoid version store issues, including possible mismatch). Should be false since lightWeightVersionStore is deprecated. version.store.onlyUseDeprecatedV1=false +# The CRON expression to trigger migration of the version store from V1 (2.x) to V2 (3.x) +# By default, this is effectively 'never' but can be modified as required. +# Examples: +# Never: * * * * * ? 2099 +# Once every thirty minutes: 0 0/30 * * * ? +# See http://www.quartz-scheduler.org/docs/tutorials/crontrigger.html +version.store.migrateVersionStore.cronExpression=* * * * * ? 2099 +# Limit number of version histories to migrate per job cycle, where -1 = unlimited. Note: if limit > 0 then need to schedule job to run regularly in order to complete the migration. +version.store.migrateVersionStore.limitPerJobCycle=-1 +version.store.migrateVersionStore.runAsScheduledJob=false + # Folders for storing people system.system_container.childname=sys:system system.people_container.childname=sys:people diff --git a/config/alfresco/scheduled-jobs-context.xml b/config/alfresco/scheduled-jobs-context.xml index c21bcee57f..a99fbf944a 100644 --- a/config/alfresco/scheduled-jobs-context.xml +++ b/config/alfresco/scheduled-jobs-context.xml @@ -410,6 +410,31 @@ + + + org.alfresco.repo.admin.patch.impl.MigrateVersionStorePatch$MigrateVersionStoreJob + + + + + + + + + + + + + + + + + + + ${version.store.migrateVersionStore.cronExpression} + + + @@ -434,6 +459,9 @@ ${version.store.migrateCleanupJob.threadCount} + + ${version.store.migrateVersionStore.runAsScheduledJob} + @@ -454,4 +482,5 @@ + diff --git a/config/alfresco/script-services-context.xml b/config/alfresco/script-services-context.xml index 800b5f4ce7..2ed0a034c4 100644 --- a/config/alfresco/script-services-context.xml +++ b/config/alfresco/script-services-context.xml @@ -145,6 +145,7 @@ + diff --git a/config/alfresco/subsystems/Authentication/common-ldap-context.xml b/config/alfresco/subsystems/Authentication/common-ldap-context.xml index 5e84cdcacd..c1d1f8d75c 100644 --- a/config/alfresco/subsystems/Authentication/common-ldap-context.xml +++ b/config/alfresco/subsystems/Authentication/common-ldap-context.xml @@ -311,6 +311,12 @@ ${ldap.synchronization.userEmailAttributeName} + + + + ${ldap.synchronization.userOrganizationalIdAttributeName} + + diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index c203e65e85..bc677987ce 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -5,9 +5,9 @@ # Version label version.major=3 -version.minor=3 -version.revision=0 -version.label=g +version.minor=4 +version.revision=1 +version.label= # Edition label @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=4102 +version.schema=4103 diff --git a/config/test/alfresco/test-hibernate-cfg.properties b/config/test/alfresco/test-hibernate-cfg.properties index c22bdc7d69..5a45d7dc0d 100644 --- a/config/test/alfresco/test-hibernate-cfg.properties +++ b/config/test/alfresco/test-hibernate-cfg.properties @@ -9,7 +9,7 @@ hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect hibernate.hbm2ddl.auto=update hibernate.jdbc.use_streams_for_binary=true hibernate.show_sql=false -hibernate.cache.use_query_cache=true +hibernate.cache.use_query_cache=false hibernate.max_fetch_depth=10 hibernate.cache.provider_class=org.alfresco.repo.cache.InternalEhCacheManagerFactoryBean hibernate.cache.use_second_level_cache=true diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java index 7d352fe7cf..3c9a74b4d2 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java @@ -24,6 +24,7 @@ import java.io.Serializable; import java.net.InetAddress; import java.util.Date; import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -77,6 +78,10 @@ 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.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.lock.LockService; import org.alfresco.service.cmr.lock.LockType; import org.alfresco.service.cmr.lock.NodeLockedException; @@ -133,6 +138,10 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa public static final String AttrLinkNode = "ContentLinkNode"; + // List of properties to copy during rename + + private static QName[] _copyProperties = { ContentModel.PROP_AUTHOR, ContentModel.PROP_TITLE, ContentModel.PROP_DESCRIPTION }; + // Services and helpers private CifsHelper cifsHelper; @@ -145,6 +154,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa private FileFolderService fileFolderService; private NodeArchiveService nodeArchiveService; private LockService lockService; + private DictionaryService dictionaryService; private AuthenticationContext authContext; private AuthenticationService authService; @@ -282,6 +292,15 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa return policyBehaviourFilter; } + /** + * Return the dictionary service + * + * @return DictionaryService + */ + public final DictionaryService getDictionaryService() { + return dictionaryService; + } + /** * @param contentService the content service */ @@ -410,6 +429,15 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa this.policyBehaviourFilter = policyFilter; } + /** + * Set the dictionary service + * + * @param dictionaryService DictionaryService + */ + public void setDictionaryService(DictionaryService dictionaryService) { + this.dictionaryService = dictionaryService; + } + /** * 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 @@ -664,6 +692,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Enable file state caching context.enableStateCache( true); + context.getStateCache().setCaseSensitive( false); // Initialize the I/O control handler @@ -2359,7 +2388,8 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa return; } else if ( logger.isDebugEnabled()) - logger.debug("Last reference to file, closing, path=" + file.getFullName() + ", access=" + file.getGrantedAccessAsString() + ", fid=" + file.getProtocolId()); + logger.debug("Last reference to file, closing, path=" + file.getFullName() + ", access=" + file.getGrantedAccessAsString() + ", fid=" + file.getProtocolId() + + ", modified=" + contentFile.isModified()); } // Check if there is a quota manager enabled @@ -2578,6 +2608,35 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa try { + // Check if pseudo files are enabled + + if ( hasPseudoFileInterface(ctx)) + { + // Check if the file name is a pseudo file name + + if ( getPseudoFileInterface( ctx).isPseudoFile(sess, tree, name)) { + + // Make sure the parent folder has a file state, and the path exists + + String[] paths = FileName.splitPath( name); + FileState fstate = ctx.getStateCache().findFileState( paths[0]); + + if ( fstate != null) { + + // Check if the path is to a pseudo file + + PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile( sess, tree, name); + if ( pfile != null) + { + // Delete the pseudo file + + getPseudoFileInterface( ctx).deletePseudoFile( sess, tree, name); + return; + } + } + } + } + // Check if there is a quota manager enabled, if so then we need to save the current file size final QuotaManager quotaMgr = ctx.getQuotaManager(); @@ -2768,7 +2827,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Get the file state for the old file, if available - final FileState oldState = ctx.getStateCache().findFileState(oldName); + final FileState oldState = ctx.getStateCache().findFileState(oldName, true); // Check if we are renaming a folder, or the rename is to a different folder @@ -2872,7 +2931,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // DEBUG if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME)) - logger.debug(" Restored node " + targetNodeRef); + logger.debug(" Restored node " + targetNodeRef + ", version=" + nodeService.getProperty( targetNodeRef, ContentModel.PROP_VERSION_LABEL)); // Check if the deleted file had a linked node, due to a rename @@ -3109,6 +3168,10 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa if ( permissionService.hasPermission(nodeRef, PermissionService.DELETE) == AccessStatus.DENIED) throw new AccessDeniedException("No delete access to " + name); + // Inhibit versioning for this transaction + + getPolicyFilter().disableBehaviour( ContentModel.ASPECT_VERSIONABLE); + // Check if the file is being marked for deletion, if so then check if the file is locked if ( info.hasSetFlag(FileInfo.SetDeleteOnClose) && info.hasDeleteOnClose()) @@ -3778,6 +3841,14 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME)) logger.debug(" Removed versionable aspect from temp file"); } + + // Copy over various properties + + for ( QName propName : _copyProperties) { + Serializable nodeProp = nodeService.getProperty( fromNode, propName); + if ( nodeProp != null) + nodeService.setProperty( toNode, propName, nodeProp); + } } /** diff --git a/source/java/org/alfresco/filesys/repo/ContentQuotaManager.java b/source/java/org/alfresco/filesys/repo/ContentQuotaManager.java index 0d9074a751..bb7f14b94e 100644 --- a/source/java/org/alfresco/filesys/repo/ContentQuotaManager.java +++ b/source/java/org/alfresco/filesys/repo/ContentQuotaManager.java @@ -31,6 +31,7 @@ import org.alfresco.jlan.server.filesys.TreeConnection; import org.alfresco.jlan.server.filesys.quota.QuotaManager; import org.alfresco.jlan.server.filesys.quota.QuotaManagerException; import org.alfresco.jlan.util.MemorySize; +import org.alfresco.jlan.util.StringList; import org.alfresco.service.cmr.usage.ContentUsageService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -411,6 +412,8 @@ public class ContentQuotaManager implements QuotaManager, Runnable { // Loop forever + StringList removeNameList = new StringList(); + m_shutdown = false; while ( m_shutdown == false) @@ -449,7 +452,8 @@ public class ContentQuotaManager implements QuotaManager, Runnable { long checkTime = System.currentTimeMillis() - UserQuotaExpireInterval; // Loop through the user quota details - + + removeNameList.remoteAllStrings(); Iterator userNames = m_liveUsage.keySet().iterator(); while ( userNames.hasNext()) { @@ -461,15 +465,25 @@ public class ContentQuotaManager implements QuotaManager, Runnable { if ( quotaDetails.getLastUpdated() < checkTime) { - // Remove the live usage tracking details, inactive + // Add the user name to the remove list, inactive - m_liveUsage.remove( userName); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Removed inactive usage tracking, " + quotaDetails); - } + removeNameList.addString( userName); + } + } + + // Remove inactive records from the live quota tracking + + while ( removeNameList.numberOfStrings() > 0) { + + // Get the current user name and remove the record + + String userName = removeNameList.removeStringAt( 0); + UserQuotaDetails quotaDetails = m_liveUsage.remove( userName); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Removed inactive usage tracking, " + quotaDetails); } } catch (Exception ex) diff --git a/source/java/org/alfresco/filesys/repo/ContentSearchContext.java b/source/java/org/alfresco/filesys/repo/ContentSearchContext.java index 4d52acd3a9..b7459757ff 100644 --- a/source/java/org/alfresco/filesys/repo/ContentSearchContext.java +++ b/source/java/org/alfresco/filesys/repo/ContentSearchContext.java @@ -132,7 +132,7 @@ public class ContentSearchContext extends SearchContext */ public int getResumeId() { - return resumeId; + return resumeId - 1; } /** @@ -411,7 +411,7 @@ public class ContentSearchContext extends SearchContext // Reset the index/resume id index = index - 1; - resumeId = resId - 1; + resumeId = resumeId - 1; donePseudoFiles = true; // DEBUG diff --git a/source/java/org/alfresco/filesys/repo/LinkMemoryNetworkFile.java b/source/java/org/alfresco/filesys/repo/LinkMemoryNetworkFile.java index 11cc49b28c..3d1b1e78aa 100644 --- a/source/java/org/alfresco/filesys/repo/LinkMemoryNetworkFile.java +++ b/source/java/org/alfresco/filesys/repo/LinkMemoryNetworkFile.java @@ -261,7 +261,7 @@ public class LinkMemoryNetworkFile extends NodeRefNetworkFile // Create a dummy file state if ( super.getFileState() == null) - setFileState(new FileState(getFullName())); + setFileState(new FileState(getFullName(), false)); return super.getFileState(); } } diff --git a/source/java/org/alfresco/repo/activities/post/lookup/PostLookup.java b/source/java/org/alfresco/repo/activities/post/lookup/PostLookup.java index f32e965a74..646e004ec6 100644 --- a/source/java/org/alfresco/repo/activities/post/lookup/PostLookup.java +++ b/source/java/org/alfresco/repo/activities/post/lookup/PostLookup.java @@ -166,12 +166,12 @@ public class PostLookup activityPost.setActivityData(activityDataStr); } - if (activityPost.getActivityData().length() > ActivityPostDAO.MAX_LEN_ACTIVITY_DATA) + if ((activityPost.getActivityData() != null) && (activityPost.getActivityData().length() > ActivityPostDAO.MAX_LEN_ACTIVITY_DATA)) { throw new IllegalArgumentException("Invalid activity data - exceeds " + ActivityPostDAO.MAX_LEN_ACTIVITY_DATA + " chars: " + activityPost.getActivityData()); } - if (activityPost.getSiteNetwork().length() > ActivityPostDAO.MAX_LEN_SITE_ID) + if ((activityPost.getSiteNetwork() != null) && (activityPost.getSiteNetwork().length() > ActivityPostDAO.MAX_LEN_SITE_ID)) { // belts-and-braces - should not get here since checked during post (and not modified) throw new IllegalArgumentException("Invalid siteId - exceeds " + ActivityPostDAO.MAX_LEN_SITE_ID + " chars: " + activityPost.getSiteNetwork()); diff --git a/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java b/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java index 6abb87331f..d810e4648d 100644 --- a/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java +++ b/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java @@ -137,53 +137,51 @@ public class RepoAdminServiceImpl implements RepoAdminService List modelsInRepo = new ArrayList(); - try - { - Collection models = dictionaryDAO.getModels(); - - List dictionaryModels = new ArrayList(); - for (QName model : models) - { - dictionaryModels.add(model.toPrefixString()); - } - - List nodeRefs = searchService.selectNodes(rootNode, repoModelsLocation.getPath()+CRITERIA_ALL+"["+defaultSubtypeOfDictionaryModel+"]", null, namespaceService, false); - - if (nodeRefs.size() > 0) - { - for (NodeRef nodeRef : nodeRefs) - { - String modelFileName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); - String repoVersion = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); - - String modelName = null; - - - ContentReader cr = contentService.getReader(nodeRef, ContentModel.TYPE_CONTENT); - InputStream is = cr.getContentInputStream(); - - M2Model model = M2Model.createModel(is); - is.close(); - - modelName = model.getName(); - - - // check against models loaded in dictionary and give warning if not found - if (dictionaryModels.contains(modelName)) - { - // note: uses dictionary model cache, rather than getting content from repo and re-compiling - modelsInRepo.add(new RepoModelDefinition(modelFileName, repoVersion, dictionaryDAO.getModel(QName.createQName(modelName, namespaceService)), true)); - } - else - { - modelsInRepo.add(new RepoModelDefinition(modelFileName, repoVersion, null, false)); - } - } - } - } - catch (Throwable t) + Collection models = dictionaryDAO.getModels(); + + List dictionaryModels = new ArrayList(); + for (QName model : models) { - throw new AlfrescoRuntimeException("Failed to get models " + t); + dictionaryModels.add(model.toPrefixString()); + } + + List nodeRefs = searchService.selectNodes(rootNode, repoModelsLocation.getPath()+CRITERIA_ALL+"["+defaultSubtypeOfDictionaryModel+"]", null, namespaceService, false); + + if (nodeRefs.size() > 0) + { + for (NodeRef nodeRef : nodeRefs) + { + String modelFileName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + String repoVersion = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); + + String modelName = null; + + ContentReader cr = contentService.getReader(nodeRef, ContentModel.TYPE_CONTENT); + InputStream is = cr.getContentInputStream(); + + try + { + M2Model model = M2Model.createModel(is); + is.close(); + + modelName = model.getName(); + + // check against models loaded in dictionary and give warning if not found + if (dictionaryModels.contains(modelName)) + { + // note: uses dictionary model cache, rather than getting content from repo and re-compiling + modelsInRepo.add(new RepoModelDefinition(modelFileName, repoVersion, dictionaryDAO.getModel(QName.createQName(modelName, namespaceService)), true)); + } + else + { + modelsInRepo.add(new RepoModelDefinition(modelFileName, repoVersion, null, false)); + } + } + catch (Throwable t) + { + logger.warn("Skip model: "+modelFileName+" ("+t.getMessage()+")"); + } + } } return modelsInRepo; diff --git a/source/java/org/alfresco/repo/admin/patch/impl/MigrateVersionStorePatch.java b/source/java/org/alfresco/repo/admin/patch/impl/MigrateVersionStorePatch.java index b581b7098f..d1cdff615b 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/MigrateVersionStorePatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/MigrateVersionStorePatch.java @@ -21,12 +21,23 @@ package org.alfresco.repo.admin.patch.impl; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.admin.patch.AbstractPatch; import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.repo.lock.JobLockService; +import org.alfresco.repo.lock.LockAcquisitionException; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.version.VersionMigrator; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.quartz.Job; +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; import org.springframework.extensions.surf.util.I18NUtil; /** @@ -36,18 +47,44 @@ public class MigrateVersionStorePatch extends AbstractPatch { private static Log logger = LogFactory.getLog(MigrateVersionStorePatch.class); + // Lock key + public static final QName LOCK = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "MigrateVersionStore"); + + // The maximum time this lock will be held for (30 mins) - unless refreshed + //public static final long LOCK_TTL = 1000 * 60 * 30; + public static final long LOCK_TTL = 30000; + + private static final String MSG_DONE = "patch.migrateVersionStore.done"; private static final String MSG_INCOMPLETE = "patch.migrateVersionStore.incomplete"; private VersionMigrator versionMigrator; private TenantService tenantService; private ImporterBootstrap version2ImporterBootstrap; + private JobLockService jobLockService; private int batchSize = 1; private int threadCount = 2; + private int limitPerJobCycle = -1; // if run as scheduled job then can limit the number of version histories to migrate (per job invocation) + + private boolean migrationComplete = false; + private boolean deleteImmediately = false; + private boolean runAsScheduledJob = false; + private boolean useDeprecatedV1 = false; + + private ThreadLocal runningAsJob = new ThreadLocal(); + + /** + * Default constructor + */ + public MigrateVersionStorePatch() + { + runningAsJob.set(Boolean.FALSE); + } + public void setVersionMigrator(VersionMigrator versionMigrator) { this.versionMigrator = versionMigrator; @@ -63,6 +100,11 @@ public class MigrateVersionStorePatch extends AbstractPatch this.version2ImporterBootstrap = version2ImporterBootstrap; } + public void setJobLockService(JobLockService jobLockService) + { + this.jobLockService = jobLockService; + } + public void setBatchSize(int batchSize) { this.batchSize = batchSize; @@ -73,11 +115,32 @@ public class MigrateVersionStorePatch extends AbstractPatch this.threadCount = threadCount; } + public void setLimitPerJobCycle(int limitPerJobCycle) + { + this.limitPerJobCycle = limitPerJobCycle; + } + public void setDeleteImmediately(boolean deleteImmediately) { this.deleteImmediately = deleteImmediately; } + /** + * Set whether the patch execution should just bypass any actual work i.e. the admin has + * chosen to manually trigger the work. + * + * @param runAsScheduledJob true to leave all work up to the scheduled job + */ + public void setRunAsScheduledJob(boolean runAsScheduledJob) + { + this.runAsScheduledJob = runAsScheduledJob; + } + + public void setOnlyUseDeprecatedV1(boolean useDeprecatedV1) + { + this.useDeprecatedV1 = useDeprecatedV1; + } + public void init() { if (batchSize < 1) @@ -97,35 +160,223 @@ public class MigrateVersionStorePatch extends AbstractPatch super.init(); } + /** + * Method called when executed as a scheduled job. + */ + private void executeViaJob() + { + AuthenticationUtil.RunAsWork patchRunAs = new AuthenticationUtil.RunAsWork() + { + public String doWork() throws Exception + { + RetryingTransactionCallback patchTxn = new RetryingTransactionCallback() + { + public String execute() throws Exception + { + try + { + runningAsJob.set(Boolean.TRUE); + String report = applyInternal(); + // done + return report; + } + finally + { + runningAsJob.set(Boolean.FALSE); // Back to default + } + } + }; + return transactionService.getRetryingTransactionHelper().doInTransaction(patchTxn); + } + }; + String report = AuthenticationUtil.runAs(patchRunAs, AuthenticationUtil.getSystemUserName()); + if (report != null) + { + logger.info(report); + } + } + + /** + * Gets a set of work to do and executes it within this transaction. + * Can be kicked off via a job or called as a patch. + */ @Override protected String applyInternal() throws Exception { - if (tenantService.isEnabled() && tenantService.isTenantUser()) + if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_READ_WRITE) { - // bootstrap new version store - StoreRef bootstrapStoreRef = version2ImporterBootstrap.getStoreRef(); - bootstrapStoreRef = tenantService.getName(AuthenticationUtil.getRunAsUser(), bootstrapStoreRef); - version2ImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); + // Nothing to do + return null; + } + + if (useDeprecatedV1) + { + // Nothing to do + return null; + } + + if (migrationComplete) + { + // Nothing to do + return null; + } + + boolean isRunningAsJob = runningAsJob.get().booleanValue(); + + // Do we bug out of patch execution + if (runAsScheduledJob && !isRunningAsJob) + { + return I18NUtil.getMessage("patch.migrateVersionStore.bypassingPatch"); + } + + // Lock + String lockToken = getLock(); + if (lockToken == null) + { + // Some other process is busy + if (isRunningAsJob) + { + // Fine, we're doing batches (or lock still present) + + if (logger.isDebugEnabled()) + { + logger.debug("Cannot get lock - an earlier job is still busy (or previous lock has not yet expired after failure - TTL was "+LOCK_TTL+" ms)"); + } + return null; + } + else + { + throw new RuntimeException("Unable to get job lock during patch execution. Only one server should perform the upgrade."); + } + } + + if (isRunningAsJob && (! this.deleteImmediately)) + { + if (logger.isDebugEnabled()) + { + logger.debug("VersionMigrator is running as a background job will immediately delete old versions (after they are migrated"); + } - version2ImporterBootstrap.bootstrap(); + this.deleteImmediately = true; } - if (AuthenticationUtil.getRunAsUser() == null) + try { - logger.info("Set system user"); - AuthenticationUtil.setRunAsUser(AuthenticationUtil.getSystemUserName()); + if (tenantService.isEnabled() && tenantService.isTenantUser()) + { + // bootstrap new version store + StoreRef bootstrapStoreRef = version2ImporterBootstrap.getStoreRef(); + + if (! nodeService.exists(bootstrapStoreRef)) + { + bootstrapStoreRef = tenantService.getName(AuthenticationUtil.getRunAsUser(), bootstrapStoreRef); + version2ImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); + version2ImporterBootstrap.bootstrap(); + } + } + + if (AuthenticationUtil.getRunAsUser() == null) + { + logger.info("Set system user"); + AuthenticationUtil.setRunAsUser(AuthenticationUtil.getSystemUserName()); + } + + Boolean migrated = versionMigrator.migrateVersions(batchSize, threadCount, limitPerJobCycle, deleteImmediately, lockToken, isRunningAsJob); + + migrationComplete = (migrated != null ? migrated : true); + + // return the result message + if (migrated != null) + { + if (migrationComplete) + { + return I18NUtil.getMessage(MSG_DONE); + } + else if (! isRunningAsJob) + { + return I18NUtil.getMessage(MSG_INCOMPLETE); + } + } + + return null; } - - boolean completed = versionMigrator.migrateVersions(batchSize, threadCount, deleteImmediately); - - // return the result message - if (completed) + finally { - return I18NUtil.getMessage(MSG_DONE); - } - else - { - return I18NUtil.getMessage(MSG_INCOMPLETE); + releaseLock(lockToken); } } -} + + /** + * Attempts to get the lock. If the lock couldn't be taken, then null is returned. + * + * @return Returns the lock token or null + */ + private String getLock() + { + String lockToken = null; + try + { + lockToken = jobLockService.getLock(LOCK, LOCK_TTL); + if (lockToken != null) + { + if (logger.isTraceEnabled()) + { + logger.trace("Got lock: "+lockToken+" with TTL of "+LOCK_TTL+" ms ["+AlfrescoTransactionSupport.getTransactionId()+"]["+Thread.currentThread().getId()+"]"); + } + } + } + catch (LockAcquisitionException e) + { + // ignore + } + return lockToken; + } + + /** + * Attempts to release the lock. + */ + private void releaseLock(String lockToken) + { + if (lockToken == null) + { + throw new IllegalArgumentException("Must provide existing lockToken"); + } + jobLockService.releaseLock(lockToken, LOCK); + + if (logger.isTraceEnabled()) + { + logger.trace("Released lock: "+lockToken+" ["+AlfrescoTransactionSupport.getTransactionId()+"]["+Thread.currentThread().getId()+"]"); + } + } + + /** + * Job to initiate the {@link MigrateVersionStorePatch} + * + * @author janv + * @since 3.3.1 + */ + public static class MigrateVersionStoreJob implements Job + { + public MigrateVersionStoreJob() + { + } + + /** + * Calls the cleaner to do its work + */ + public void execute(JobExecutionContext context) throws JobExecutionException + { + JobDataMap jobData = context.getJobDetail().getJobDataMap(); + + // extract the migrator to use + Object migrateVersionStoreObj = jobData.get("migrateVersionStore"); + if (migrateVersionStoreObj == null || !(migrateVersionStoreObj instanceof MigrateVersionStorePatch)) + { + throw new AlfrescoRuntimeException("'migrateVersionStore' data must contain valid 'MigrateVersionStore' reference"); + } + + MigrateVersionStorePatch migrateVersionStore = (MigrateVersionStorePatch) migrateVersionStoreObj; + migrateVersionStore.executeViaJob(); + } + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/avm/AVMDeploymentAttemptCleanerTest.java b/source/java/org/alfresco/repo/avm/AVMDeploymentAttemptCleanerTest.java index 12ce024cac..7d78c8886b 100644 --- a/source/java/org/alfresco/repo/avm/AVMDeploymentAttemptCleanerTest.java +++ b/source/java/org/alfresco/repo/avm/AVMDeploymentAttemptCleanerTest.java @@ -21,13 +21,11 @@ package org.alfresco.repo.avm; import junit.framework.TestCase; -import org.alfresco.repo.avm.AVMDeploymentAttemptCleaner; import org.alfresco.repo.importer.ImporterBootstrap; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.ApplicationContextHelper; import org.springframework.context.ApplicationContext; /** @@ -37,7 +35,7 @@ import org.springframework.context.ApplicationContext; */ public class AVMDeploymentAttemptCleanerTest extends TestCase { - private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + private static ApplicationContext ctx = AVMTestSuite.getContext(); private AVMDeploymentAttemptCleaner cleaner; diff --git a/source/java/org/alfresco/repo/avm/AVMExpiredContentTest.java b/source/java/org/alfresco/repo/avm/AVMExpiredContentTest.java index e85a516fc1..2e1f6a50f1 100644 --- a/source/java/org/alfresco/repo/avm/AVMExpiredContentTest.java +++ b/source/java/org/alfresco/repo/avm/AVMExpiredContentTest.java @@ -29,7 +29,6 @@ import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.workflow.WorkflowService; import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.ApplicationContextHelper; import org.springframework.context.ApplicationContext; /** @@ -39,7 +38,7 @@ import org.springframework.context.ApplicationContext; */ public class AVMExpiredContentTest extends TestCase { - private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + private static ApplicationContext ctx = AVMTestSuite.getContext(); private AVMExpiredContentProcessor processor; diff --git a/source/java/org/alfresco/repo/avm/AVMServiceLocalTest.java b/source/java/org/alfresco/repo/avm/AVMServiceLocalTest.java index 710827b1ac..ae0ed053f3 100644 --- a/source/java/org/alfresco/repo/avm/AVMServiceLocalTest.java +++ b/source/java/org/alfresco/repo/avm/AVMServiceLocalTest.java @@ -46,7 +46,6 @@ import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.remote.AVMRemote; import org.alfresco.service.cmr.security.AuthenticationService; import org.alfresco.service.namespace.QName; -import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.NameMatcher; import org.alfresco.util.Pair; import org.apache.commons.logging.Log; @@ -60,30 +59,31 @@ public class AVMServiceLocalTest extends TestCase { private static Log logger = LogFactory.getLog(AVMServiceLocalTest.class); + /** + * The application context. + */ + protected static ApplicationContext fContext; + /** * The AVMRemote - can be local (AVMRemoteLocal) or remote (AVMRemote) */ - protected AVMRemote fService; - + protected static AVMRemote fService; + /** * The AVMSyncService - can be local (AVMSyncService) or remote (AVMSyncServiceRemote) */ - protected AVMSyncService fSyncService; + protected static AVMSyncService fSyncService; - /** - * The application context. - */ - protected ApplicationContext fContext; - - protected NameMatcher excluder; - + protected static NameMatcher excluder; + + @Override protected void setUp() throws Exception { if (fContext == null) { // local (embedded) test setup - fContext = ApplicationContextHelper.getApplicationContext(); + fContext = AVMTestSuite.getContext(); fService = (AVMRemote)fContext.getBean("avmRemote"); fSyncService = (AVMSyncService)fContext.getBean("AVMSyncService"); excluder = (NameMatcher) fContext.getBean("globalPathExcluder"); diff --git a/source/java/org/alfresco/repo/avm/AVMServicePermissionsTest.java b/source/java/org/alfresco/repo/avm/AVMServicePermissionsTest.java index 52610243d1..7b2bade48c 100644 --- a/source/java/org/alfresco/repo/avm/AVMServicePermissionsTest.java +++ b/source/java/org/alfresco/repo/avm/AVMServicePermissionsTest.java @@ -60,7 +60,6 @@ import org.alfresco.service.namespace.NamespacePrefixResolver; 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.springframework.context.ApplicationContext; import org.springframework.orm.hibernate3.LocalSessionFactoryBean; @@ -71,8 +70,8 @@ import org.springframework.orm.hibernate3.LocalSessionFactoryBean; */ public class AVMServicePermissionsTest extends TestCase { - private static ApplicationContext applicationContext = ApplicationContextHelper.getApplicationContext(); - + private static ApplicationContext applicationContext = AVMTestSuite.getContext(); + protected NodeService nodeService; protected DictionaryService dictionaryService; diff --git a/source/java/org/alfresco/repo/avm/AVMServiceTestBase.java b/source/java/org/alfresco/repo/avm/AVMServiceTestBase.java index 8810ef9494..cbbb5db0bd 100644 --- a/source/java/org/alfresco/repo/avm/AVMServiceTestBase.java +++ b/source/java/org/alfresco/repo/avm/AVMServiceTestBase.java @@ -43,7 +43,6 @@ import org.alfresco.service.cmr.search.ResultSetRow; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.AuthenticationService; import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.ApplicationContextHelper; import org.springframework.context.ApplicationContext; /** @@ -90,6 +89,7 @@ public class AVMServiceTestBase extends TestCase protected static AuthenticationService fAuthService; + public void testSetup() throws Exception { setupBasicTree(); @@ -105,7 +105,8 @@ public class AVMServiceTestBase extends TestCase { if (fContext == null) { - fContext = ApplicationContextHelper.getApplicationContext(); + fContext = AVMTestSuite.getContext(); + fService = (AVMService)fContext.getBean("AVMService"); fReaper = (OrphanReaper)fContext.getBean("orphanReaper"); fSyncService = (AVMSyncService)fContext.getBean("AVMSyncService"); diff --git a/source/java/org/alfresco/repo/avm/AVMTestSuite.java b/source/java/org/alfresco/repo/avm/AVMTestSuite.java index a4d8c3f0be..94e7ce35f9 100644 --- a/source/java/org/alfresco/repo/avm/AVMTestSuite.java +++ b/source/java/org/alfresco/repo/avm/AVMTestSuite.java @@ -37,9 +37,14 @@ public class AVMTestSuite extends TestSuite { ApplicationContextHelper.setUseLazyLoading(false); ApplicationContextHelper.setNoAutoStart(true); + + /* return ApplicationContextHelper.getApplicationContext( new String[] { "classpath:alfresco/minimal-context.xml" } ); + */ + + return ApplicationContextHelper.getApplicationContext(); } /** @@ -82,9 +87,10 @@ public class AVMTestSuite extends TestSuite suite.addTestSuite(VersionPathTest.class); suite.addTestSuite(WCMInheritPermissionsTest.class); - // This should go last, as its uses a different - // context to the other tests + /* + // note:to test remotely need running repo (otherwise effectively repeats AVMServiceLocalTest) suite.addTestSuite(AVMServiceRemoteSystemTest.class); + */ return suite; } diff --git a/source/java/org/alfresco/repo/avm/AbstractSpringContextTest.java b/source/java/org/alfresco/repo/avm/AbstractSpringContextTest.java deleted file mode 100644 index e48b7b13e0..0000000000 --- a/source/java/org/alfresco/repo/avm/AbstractSpringContextTest.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.avm; - -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.avm.AVMService; -import org.alfresco.service.cmr.security.AuthenticationService; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.util.ApplicationContextHelper; -import org.springframework.test.AbstractDependencyInjectionSpringContextTests; - -public abstract class AbstractSpringContextTest extends AbstractDependencyInjectionSpringContextTests -{ - protected AVMService avmService; - protected AuthenticationService authenticationService; - protected ServiceRegistry servReg; - protected PermissionService permissionService; - - @Override - protected void onSetUp() throws Exception - { - super.onSetUp(); - servReg = (ServiceRegistry) applicationContext.getBean(ServiceRegistry.SERVICE_REGISTRY); - avmService = servReg.getAVMService(); - assertNotNull(avmService); - authenticationService = servReg.getAuthenticationService(); - assertNotNull(authenticationService); - permissionService = servReg.getPermissionService(); - assertNotNull(permissionService); - - authenticationService.authenticate(AuthenticationUtil.getAdminUserName(), "admin".toCharArray()); - } - - @Override - protected void onTearDown() throws Exception - { - super.onTearDown(); - } - - @Override - protected String[] getConfigLocations() - { - return ApplicationContextHelper.CONFIG_LOCATIONS; - } -} diff --git a/source/java/org/alfresco/repo/avm/WCMInheritPermissionsTest.java b/source/java/org/alfresco/repo/avm/WCMInheritPermissionsTest.java index 71dc63c8bf..b83dfe3842 100644 --- a/source/java/org/alfresco/repo/avm/WCMInheritPermissionsTest.java +++ b/source/java/org/alfresco/repo/avm/WCMInheritPermissionsTest.java @@ -21,54 +21,64 @@ package org.alfresco.repo.avm; import org.alfresco.config.JNDIConstants; import org.alfresco.service.cmr.avm.AVMNodeDescriptor; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.PermissionService; -public class WCMInheritPermissionsTest extends AbstractSpringContextTest +public class WCMInheritPermissionsTest extends AVMServiceTestBase { private static final String FILE_NAME = "fileForExport"; private static final String STORE_NAME = "TestStore1"; private static final String ROOT = "ROOT"; - - private void createStaggingWithSnapshots(String storeName) + + protected PermissionService permissionService; + + @Override + protected void setUp() throws Exception { - if (avmService.getStore(storeName) != null) + super.setUp(); + + permissionService = (PermissionService)fContext.getBean("permissionService"); + } + + private void createStagingWithSnapshots(String storeName) + { + if (fService.getStore(storeName) != null) { - avmService.purgeStore(storeName); + fService.purgeStore(storeName); } - - avmService.createStore(storeName); - assertNotNull(avmService.getStore(storeName)); - - avmService.createDirectory(storeName + ":/", JNDIConstants.DIR_DEFAULT_WWW); - avmService.createSnapshot(storeName, "first", "first"); - assertNotNull(avmService.lookup(-1, storeName + ":/" + JNDIConstants.DIR_DEFAULT_WWW)); - avmService.createDirectory(storeName + ":/" + JNDIConstants.DIR_DEFAULT_WWW, JNDIConstants.DIR_DEFAULT_APPBASE); - avmService.createSnapshot(storeName, "second", "second"); - assertNotNull(avmService.lookup(-1, storeName + ":/" + JNDIConstants.DIR_DEFAULT_WWW + "/" + JNDIConstants.DIR_DEFAULT_APPBASE)); - avmService.createDirectory(storeName + ":/" + JNDIConstants.DIR_DEFAULT_WWW + "/" + JNDIConstants.DIR_DEFAULT_APPBASE, ROOT); - avmService.createSnapshot(storeName, "third", "third"); - assertNotNull(avmService.lookup(-1, storeName + ":/" + JNDIConstants.DIR_DEFAULT_WWW + "/" + JNDIConstants.DIR_DEFAULT_APPBASE + "/" + ROOT)); - avmService.createFile(storeName + ":/" + JNDIConstants.DIR_DEFAULT_WWW + "/" + JNDIConstants.DIR_DEFAULT_APPBASE + "/" + ROOT, FILE_NAME); - avmService.createSnapshot(storeName, "fourth", "fourth"); - assertNotNull(avmService.lookup(-1, storeName + ":/" + JNDIConstants.DIR_DEFAULT_WWW + "/" + JNDIConstants.DIR_DEFAULT_APPBASE + "/" + ROOT + "/" + FILE_NAME)); - + + fService.createStore(storeName); + assertNotNull(fService.getStore(storeName)); + + fService.createDirectory(storeName + ":/", JNDIConstants.DIR_DEFAULT_WWW); + fService.createSnapshot(storeName, "first", "first"); + assertNotNull(fService.lookup(-1, storeName + ":/" + JNDIConstants.DIR_DEFAULT_WWW)); + fService.createDirectory(storeName + ":/" + JNDIConstants.DIR_DEFAULT_WWW, JNDIConstants.DIR_DEFAULT_APPBASE); + fService.createSnapshot(storeName, "second", "second"); + assertNotNull(fService.lookup(-1, storeName + ":/" + JNDIConstants.DIR_DEFAULT_WWW + "/" + JNDIConstants.DIR_DEFAULT_APPBASE)); + fService.createDirectory(storeName + ":/" + JNDIConstants.DIR_DEFAULT_WWW + "/" + JNDIConstants.DIR_DEFAULT_APPBASE, ROOT); + fService.createSnapshot(storeName, "third", "third"); + assertNotNull(fService.lookup(-1, storeName + ":/" + JNDIConstants.DIR_DEFAULT_WWW + "/" + JNDIConstants.DIR_DEFAULT_APPBASE + "/" + ROOT)); + fService.createFile(storeName + ":/" + JNDIConstants.DIR_DEFAULT_WWW + "/" + JNDIConstants.DIR_DEFAULT_APPBASE + "/" + ROOT, FILE_NAME); + fService.createSnapshot(storeName, "fourth", "fourth"); + assertNotNull(fService.lookup(-1, storeName + ":/" + JNDIConstants.DIR_DEFAULT_WWW + "/" + JNDIConstants.DIR_DEFAULT_APPBASE + "/" + ROOT + "/" + FILE_NAME)); } private void removeStore(String storeName) { - avmService.purgeStore(storeName); - assertNull(avmService.getStore(storeName)); + fService.purgeStore(storeName); + assertNull(fService.getStore(storeName)); } - + public void testSetInheritParentPermissions() { - createStaggingWithSnapshots(STORE_NAME); - - AVMNodeDescriptor nodeDescriptor = avmService.lookup(-1, STORE_NAME + ":/" + JNDIConstants.DIR_DEFAULT_WWW + "/" + JNDIConstants.DIR_DEFAULT_APPBASE + "/" + ROOT + "/" + createStagingWithSnapshots(STORE_NAME); + + AVMNodeDescriptor nodeDescriptor = fService.lookup(-1, STORE_NAME + ":/" + JNDIConstants.DIR_DEFAULT_WWW + "/" + JNDIConstants.DIR_DEFAULT_APPBASE + "/" + ROOT + "/" + FILE_NAME); assertNotNull(nodeDescriptor); NodeRef nodeRef = AVMNodeConverter.ToNodeRef(-1, nodeDescriptor.getPath()); assertNotNull(nodeRef); - + permissionService.setInheritParentPermissions(nodeRef, false); assertFalse(permissionService.getInheritParentPermissions(nodeRef)); permissionService.setInheritParentPermissions(nodeRef, true); diff --git a/source/java/org/alfresco/repo/avm/locking/AVMLockingServiceTest.java b/source/java/org/alfresco/repo/avm/locking/AVMLockingServiceTest.java index 45b500ea94..1862909109 100644 --- a/source/java/org/alfresco/repo/avm/locking/AVMLockingServiceTest.java +++ b/source/java/org/alfresco/repo/avm/locking/AVMLockingServiceTest.java @@ -30,6 +30,7 @@ import junit.framework.TestCase; import org.alfresco.model.ContentModel; import org.alfresco.model.WCMAppModel; +import org.alfresco.repo.avm.AVMTestSuite; import org.alfresco.repo.avm.util.BulkLoader; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.domain.PropertyValue; @@ -99,7 +100,8 @@ public class AVMLockingServiceTest extends TestCase { if (fContext == null) { - fContext = ApplicationContextHelper.getApplicationContext(); + fContext = AVMTestSuite.getContext(); + fLockingService = (AVMLockingService)fContext.getBean("AVMLockingService"); fService = (AVMService) fContext.getBean("AVMLockingAwareService"); fSyncService = (AVMSyncService)fContext.getBean("AVMSyncService"); diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java b/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java index cf6eda34ae..6a5e94ce5e 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryDAOImpl.java @@ -936,14 +936,7 @@ public class DictionaryDAOImpl implements DictionaryDAO { CompiledModel model = getCompiledModel(modelName); ModelDefinition modelDef = model.getModelDefinition(); - - List namespaces = new ArrayList(); - for (M2Namespace namespace : model.getM2Model().getNamespaces()) - { - namespaces.add(new M2NamespaceDefinition(modelDef, namespace.getUri(), namespace.getPrefix())); - } - - return namespaces; + return modelDef.getNamespaces(); } /* (non-Javadoc) diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java b/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java index 04fc6fcb71..09de1255a7 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryModelType.java @@ -30,6 +30,7 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.ContentServicePolicies; import org.alfresco.repo.lock.JobLockService; +import org.alfresco.repo.lock.LockAcquisitionException; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; @@ -68,6 +69,7 @@ import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.GUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.dao.ConcurrencyFailureException; /** * Dictionary model type behaviour. @@ -494,7 +496,7 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda if (logger.isTraceEnabled()) { - logger.trace("afterCommit: pendingModelsCnt="+(pendingModels != null ? pendingModels.size() : "0")+ + logger.trace("afterCommit: "+Thread.currentThread().getName()+" pendingModelsCnt="+(pendingModels != null ? pendingModels.size() : "0")+ ", pendingDeleteModelsCnt="+(pendingDeleteModels != null ? pendingDeleteModels.size() : "0")); } @@ -569,11 +571,23 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda { if (jobLockService != null) { - jobLockService.getTransactionalLock(LOCK_QNAME, (1000*60), 3000, 10); + if (logger.isTraceEnabled()) + { + logger.trace("beforeCommit: "+Thread.currentThread().getName()+" attempt to get transactional lock ["+AlfrescoTransactionSupport.getTransactionId()+"]"); + } + + try + { + jobLockService.getTransactionalLock(LOCK_QNAME, (1000*60), 3000, 10); + } + catch (LockAcquisitionException lae) + { + throw new ConcurrencyFailureException(lae.getMessage()); + } if (logger.isTraceEnabled()) { - logger.trace(Thread.currentThread().getName()+" got transactional lock "); + logger.trace("beforeCommit: "+Thread.currentThread().getName()+" got transactional lock ["+AlfrescoTransactionSupport.getTransactionId()+"]"); } } @@ -583,7 +597,7 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda { if (logger.isTraceEnabled()) { - logger.trace("beforeCommit: pendingModelsCnt="+pendingModels.size()); + logger.trace("beforeCommit: pendingModelsCnt="+pendingModels.size()+" ["+AlfrescoTransactionSupport.getTransactionId()+"]"); } for (NodeRef pendingNodeRef : pendingModels) @@ -646,7 +660,7 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda if (logger.isTraceEnabled()) { - logger.trace("beforeCommit: activating nodeRef="+nodeRef+" ("+modelDefinition.getName()+")"); + logger.trace("beforeCommit: activating nodeRef="+nodeRef+" ("+modelDefinition.getName()+") ["+AlfrescoTransactionSupport.getTransactionId()+"]"); } } } @@ -663,7 +677,7 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda if (logger.isTraceEnabled()) { - logger.trace("beforeCommit: deactivating nodeRef="+nodeRef+" ("+modelName+")"); + logger.trace("beforeCommit: deactivating nodeRef="+nodeRef+" ("+modelName+") ["+AlfrescoTransactionSupport.getTransactionId()+"]"); } } } @@ -708,9 +722,15 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda // TODO add model locking during delete (would need to be tenant-aware & cluster-aware) to avoid potential // for concurrent addition of new content/workflow as model is being deleted + final Collection namespaceDefs; + final Collection typeDefs; + final Collection aspectDefs; + try { - dictionaryDAO.getModel(modelName); // ignore returned model definition + namespaceDefs = dictionaryDAO.getNamespaces(modelName); + typeDefs = dictionaryDAO.getTypes(modelName); + aspectDefs = dictionaryDAO.getAspects(modelName); } catch (DictionaryException e) { @@ -722,7 +742,7 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda } // TODO - in case of MT we do not currently allow deletion of an overridden model (with usages) ... but could allow if (re-)inherited model is equivalent to an incremental update only ? - validateModelDelete(modelName, false); + validateModelDelete(namespaceDefs, typeDefs, aspectDefs, false); if (tenantService.isEnabled() && tenantService.isTenantUser() == false) { @@ -737,7 +757,7 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda { if (dictionaryDAO.isModelInherited(modelName)) { - validateModelDelete(modelName, true); + validateModelDelete(namespaceDefs, typeDefs, aspectDefs, true); } return null; } @@ -746,38 +766,46 @@ public class DictionaryModelType implements ContentServicePolicies.OnContentUpda } } - private void validateModelDelete(QName modelName, boolean sharedModel) + private void validateModelDelete(Collection namespaceDefs, Collection typeDefs, Collection aspectDefs, boolean sharedModel) { - String tenantDomain = TenantService.DEFAULT_DOMAIN; + String tenantDomain = TenantService.DEFAULT_DOMAIN; if (sharedModel) { tenantDomain = " for tenant [" + tenantService.getCurrentUserDomain() + "]"; } - // check workflow namespace usage - for (WorkflowDefinition workflowDef : workflowService.getDefinitions()) + List workflowDefs = workflowService.getDefinitions(); + + if (workflowDefs.size() > 0) { - String workflowDefName = workflowDef.getName(); - String workflowNamespaceURI = QName.createQName(BPMEngineRegistry.getLocalId(workflowDefName), namespaceService).getNamespaceURI(); - for (NamespaceDefinition namespace : dictionaryDAO.getNamespaces(modelName)) + if (namespaceDefs.size() > 0) { - if (workflowNamespaceURI.equals(namespace.getUri())) + // check workflow namespace usage + for (WorkflowDefinition workflowDef : workflowDefs) { - throw new AlfrescoRuntimeException("Failed to validate model delete" + tenantDomain + " - found workflow process definition " + workflowDefName + " using model namespace '" + namespace.getUri() + "'"); + String workflowDefName = workflowDef.getName(); + String workflowNamespaceURI = QName.createQName(BPMEngineRegistry.getLocalId(workflowDefName), namespaceService).getNamespaceURI(); + for (NamespaceDefinition namespaceDef : namespaceDefs) + { + if (workflowNamespaceURI.equals(namespaceDef.getUri())) + { + throw new AlfrescoRuntimeException("Failed to validate model delete" + tenantDomain + " - found workflow process definition " + workflowDefName + " using model namespace '" + namespaceDef.getUri() + "'"); + } + } } } } - + // check for type usages - for (TypeDefinition type : dictionaryDAO.getTypes(modelName)) + for (TypeDefinition type : typeDefs) { - validateClass(tenantDomain, type); + validateClass(tenantDomain, type); } - + // check for aspect usages - for (AspectDefinition aspect : dictionaryDAO.getAspects(modelName)) + for (AspectDefinition aspect : aspectDefs) { - validateClass(tenantDomain, aspect); + validateClass(tenantDomain, aspect); } } diff --git a/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java b/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java index f4915d6e1c..89971ecce6 100644 --- a/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java +++ b/source/java/org/alfresco/repo/dictionary/M2ClassDefinition.java @@ -83,6 +83,10 @@ import org.alfresco.util.EqualsHelper; // Resolve Names this.name = QName.createQName(m2Class.getName(), resolver); + if (!model.isNamespaceDefined(name.getNamespaceURI())) + { + throw new DictionaryException("Cannot define class " + name.toPrefixString() + " as namespace " + name.getNamespaceURI() + " is not defined by model " + model.getName().toPrefixString()); + } this.archive = m2Class.getArchive(); this.includedInSuperTypeQuery = m2Class.getIncludedInSuperTypeQuery(); if (m2Class.getParentName() != null && m2Class.getParentName().length() > 0) @@ -94,6 +98,10 @@ import org.alfresco.util.EqualsHelper; for (M2Property property : m2Class.getProperties()) { PropertyDefinition def = new M2PropertyDefinition(this, property, resolver); + if (!model.isNamespaceDefined(def.getName().getNamespaceURI())) + { + throw new DictionaryException("Cannot define property " + def.getName().toPrefixString() + " as namespace " + def.getName().getNamespaceURI() + " is not defined by model " + model.getName().toPrefixString()); + } if (properties.containsKey(def.getName())) { throw new DictionaryException("Found duplicate property definition " + def.getName().toPrefixString() + " within class " + name.toPrefixString()); @@ -124,6 +132,10 @@ import org.alfresco.util.EqualsHelper; { def = new M2AssociationDefinition(this, assoc, resolver); } + if (!model.isNamespaceDefined(def.getName().getNamespaceURI())) + { + throw new DictionaryException("Cannot define association " + def.getName().toPrefixString() + " as namespace " + def.getName().getNamespaceURI() + " is not defined by model " + model.getName().toPrefixString()); + } if (associations.containsKey(def.getName())) { throw new DictionaryException("Found duplicate association definition " + def.getName().toPrefixString() + " within class " + name.toPrefixString()); diff --git a/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java b/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java index 41bcd8507e..cc63b0492a 100644 --- a/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java +++ b/source/java/org/alfresco/repo/dictionary/M2ConstraintDefinition.java @@ -99,6 +99,10 @@ import org.springframework.beans.PropertyAccessException; else { this.name = QName.createQName(m2Constraint.getName(), prefixResolver); + if (!model.isNamespaceDefined(name.getNamespaceURI())) + { + throw new DictionaryException("Cannot define constraint " + name.toPrefixString() + " as namespace " + name.getNamespaceURI() + " is not defined by model " + model.getName().toPrefixString()); + } } } diff --git a/source/java/org/alfresco/repo/dictionary/M2DataTypeDefinition.java b/source/java/org/alfresco/repo/dictionary/M2DataTypeDefinition.java index 4f5b7c105c..1df59f90d8 100644 --- a/source/java/org/alfresco/repo/dictionary/M2DataTypeDefinition.java +++ b/source/java/org/alfresco/repo/dictionary/M2DataTypeDefinition.java @@ -45,6 +45,10 @@ import org.alfresco.service.namespace.QName; { this.model = model; this.name = QName.createQName(propertyType.getName(), resolver); + if (!model.isNamespaceDefined(name.getNamespaceURI())) + { + throw new DictionaryException("Cannot define data type " + name.toPrefixString() + " as namespace " + name.getNamespaceURI() + " is not defined by model " + model.getName().toPrefixString()); + } this.dataType = propertyType; } diff --git a/source/java/org/alfresco/repo/dictionary/M2ModelDefinition.java b/source/java/org/alfresco/repo/dictionary/M2ModelDefinition.java index b85a28276d..f2317f93b0 100644 --- a/source/java/org/alfresco/repo/dictionary/M2ModelDefinition.java +++ b/source/java/org/alfresco/repo/dictionary/M2ModelDefinition.java @@ -18,9 +18,13 @@ */ package org.alfresco.repo.dictionary; +import java.util.ArrayList; +import java.util.Collection; import java.util.Date; +import java.util.List; import org.alfresco.service.cmr.dictionary.ModelDefinition; +import org.alfresco.service.cmr.dictionary.NamespaceDefinition; import org.alfresco.service.namespace.NamespacePrefixResolver; import org.alfresco.service.namespace.QName; @@ -91,5 +95,60 @@ public class M2ModelDefinition implements ModelDefinition { return model.getVersion(); } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.dictionary.ModelDefinition#getNamespaces() + */ + public Collection getNamespaces() + { + List namespaces = new ArrayList(); + for (M2Namespace namespace : model.getNamespaces()) + { + namespaces.add(new M2NamespaceDefinition(this, namespace.getUri(), namespace.getPrefix())); + } + return namespaces; + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.dictionary.ModelDefinition#isNamespaceDefined(java.lang.String) + */ + public boolean isNamespaceDefined(String uri) + { + for (M2Namespace namespace : model.getNamespaces()) + { + if (namespace.getUri().equals(uri)) + { + return true; + } + } + return false; + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.dictionary.ModelDefinition#getImportedNamespaces() + */ + public Collection getImportedNamespaces() + { + List namespaces = new ArrayList(); + for (M2Namespace namespace : model.getImports()) + { + namespaces.add(new M2NamespaceDefinition(this, namespace.getUri(), namespace.getPrefix())); + } + return namespaces; + } + /* (non-Javadoc) + * @see org.alfresco.service.cmr.dictionary.ModelDefinition#isNamespaceImported(java.lang.String) + */ + public boolean isNamespaceImported(String uri) + { + for (M2Namespace namespace : model.getImports()) + { + if (namespace.getUri().equals(uri)) + { + return true; + } + } + return false; + } } diff --git a/source/java/org/alfresco/repo/dictionary/TestModel.java b/source/java/org/alfresco/repo/dictionary/TestModel.java index c2d6b6ea35..4f0c05a7cc 100644 --- a/source/java/org/alfresco/repo/dictionary/TestModel.java +++ b/source/java/org/alfresco/repo/dictionary/TestModel.java @@ -57,6 +57,7 @@ public class TestModel bootstrapModels.add("alfresco/model/wcmModel.xml"); bootstrapModels.add("alfresco/model/applicationModel.xml"); bootstrapModels.add("alfresco/model/bpmModel.xml"); + bootstrapModels.add("alfresco/model/wcmAppModel.xml"); // include models specified on command line for (String arg: args) diff --git a/source/java/org/alfresco/repo/domain/hibernate/Permission.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/Permission.hbm.xml index 95df4ed8ed..499efa20fb 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/Permission.hbm.xml +++ b/source/java/org/alfresco/repo/domain/hibernate/Permission.hbm.xml @@ -145,7 +145,11 @@ - + + + + - - props = this.nodeService.getProperties(newNode); - String newName = (String)props.get(ContentModel.PROP_NAME); - String newTitle = (String)props.get(ContentModel.PROP_TITLE); - assertEquals(name, newName); - assertEquals(title, newTitle); + // check the node was created correctly + checkContentDetails(newNode, name, title, mimetype, content); - ContentData contentData = (ContentData) this.nodeService.getProperty(newNode, ContentModel.PROP_CONTENT); + // create another node without supplying the mimetype and check the details + String name2 = "created2-" + this.documentName; + data = createContentFormData(name2, title, null, content); + data.addFieldData(TypeFormProcessor.DESTINATION, this.folder.toString()); + NodeRef newNode2 = (NodeRef)this.formService.saveForm(new Item(TYPE_FORM_ITEM_KIND, "cm:content"), data); + checkContentDetails(newNode2, name2, title, "text/plain", content); + + // update the content and the mimetype + Item item = new Item(NODE_FORM_ITEM_KIND, newNode.toString()); + String updatedContent = "This is the updated content"; + String updatedMimetype = "text/plain"; + data = createContentFormData(name, title, updatedMimetype, updatedContent); + this.formService.saveForm(item, data); + + // check the node was updated correctly + checkContentDetails(newNode, name, title, updatedMimetype, updatedContent); + + // update the content and mimetype again but ensure the content is updated last + // to check the mimetype change is not lost + updatedContent = "The content is now XML"; + updatedMimetype = "text/xml"; + data = createContentFormData(name, title, updatedMimetype, updatedContent); + // remove and add content to ensure it's last + data.removeFieldData("prop_cm_content"); + data.addFieldData("prop_cm_content", updatedContent); + this.formService.saveForm(item, data); + + // check the details + checkContentDetails(newNode, name, title, updatedMimetype, updatedContent); + + // update just the content + updatedContent = "The content is still XML"; + data = createContentFormData(null, null, null, updatedContent); + this.formService.saveForm(item, data); + checkContentDetails(newNode, name, title, updatedMimetype, updatedContent); + } + + private FormData createContentFormData(String name, String title, String mimetype, String content) + { + FormData data = new FormData(); + + if (name != null) + { + data.addFieldData("prop_cm_name", name); + } + + if (title != null) + { + data.addFieldData("prop_cm_title", title); + } + + if (content != null) + { + data.addFieldData("prop_cm_content", content); + } + + if (mimetype != null) + { + data.addFieldData("prop_mimetype", mimetype); + } + + return data; + } + + private void checkContentDetails(NodeRef node, String expectedName, String expectedTitle, + String expectedMimetype, String expectedContent) + { + Map props = this.nodeService.getProperties(node); + String name = (String)props.get(ContentModel.PROP_NAME); + String title = (String)props.get(ContentModel.PROP_TITLE); + assertEquals(expectedName, name); + assertEquals(expectedTitle, title); + + ContentData contentData = (ContentData) this.nodeService.getProperty(node, ContentModel.PROP_CONTENT); assertNotNull(contentData); - String newMimetype = contentData.getMimetype(); - assertEquals(mimetype, newMimetype); + String mimetype = contentData.getMimetype(); + assertEquals(expectedMimetype, mimetype); - ContentReader reader = this.contentService.getReader(newNode, ContentModel.PROP_CONTENT); + ContentReader reader = this.contentService.getReader(node, ContentModel.PROP_CONTENT); assertNotNull(reader); - String newContent = reader.getContentString(); - assertEquals(content, newContent); + String content = reader.getContentString(); + assertEquals(expectedContent, content); + } + + @SuppressWarnings({ "deprecation", "null", "unchecked" }) + public void disabledTestFDKModel() throws Exception + { + // NOTE: The FDK is not loaded by default, for this test to work you must + // import and make the "Forms Development Kit" project a dependency + // of the "Repository" project. + + DictionaryService dictionary = (DictionaryService)this.applicationContext.getBean("DictionaryService"); + try + { + dictionary.getType(QName.createQName("fdk", "everything", this.namespaceService)); + } + catch (NamespaceException ne) + { + fail("FDK namespace is missing, ensure you've made the 'Forms Development Kit' project a dependency of the 'Repository' project when enabling this test!"); + } + + // from the check above we know the 'fdk' namespace is present so we can safely + // use the FDK model, firstly create an instance of an everything node + String fdkUri = "http://www.alfresco.org/model/fdk/1.0"; + QName everythingType = QName.createQName(fdkUri, "everything"); + QName textProperty = QName.createQName(fdkUri, "text"); + QName underscoreProperty = QName.createQName(fdkUri, "with_underscore"); + QName dashProperty = QName.createQName(fdkUri, "with-dash"); + QName duplicateProperty = QName.createQName(fdkUri, "duplicate"); + + String guid = GUID.generate(); + String name = "everything" + guid + ".txt"; + String textValue = "This is some text."; + String underscoreValue = "Property with an underscore in the name."; + String dashValue = "Property with a dash in the name."; + String duplicateValue = "Property with the same name as an association."; + + Map docProps = new HashMap(4); + docProps.put(ContentModel.PROP_NAME, name); + docProps.put(textProperty, textValue); + docProps.put(underscoreProperty, underscoreValue); + docProps.put(dashProperty, dashValue); + docProps.put(duplicateProperty, duplicateValue); + NodeRef everythingNode = this.nodeService.createNode(this.folder, ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, name), everythingType, + docProps).getChildRef(); + + // define a list of fields to retrieve from the node + List fields = new ArrayList(4); + fields.add("cm:name"); + fields.add("fdk:text"); + fields.add("fdk:with_underscore"); + fields.add("fdk:with-dash"); + fields.add("prop:fdk:duplicate"); + fields.add("assoc:fdk:duplicate"); + + Form form = this.formService.getForm(new Item(NODE_FORM_ITEM_KIND, everythingNode.toString()), fields); + + // check a form got returned + assertNotNull("Expecting form to be present", form); + + // check the type is correct + assertEquals("fdk:everything", form.getItem().getType()); + + // check the field definitions + Collection fieldDefs = form.getFieldDefinitions(); + assertNotNull("Expecting to find fields", fieldDefs); + assertEquals("Expecting to find " + fields.size() + " fields", fields.size(), fieldDefs.size()); + + // find the fields, as we have a duplicate we can't use a Map + PropertyFieldDefinition nameField = null; + PropertyFieldDefinition textField = null; + PropertyFieldDefinition underscoreField = null; + PropertyFieldDefinition dashField = null; + PropertyFieldDefinition duplicatePropField = null; + AssociationFieldDefinition duplicateAssocField = null; + + for (FieldDefinition field : fieldDefs) + { + if (field.getName().equals("cm:name")) + { + nameField = (PropertyFieldDefinition)field; + } + else if (field.getName().equals("fdk:text")) + { + textField = (PropertyFieldDefinition)field; + } + else if (field.getName().equals("fdk:with_underscore")) + { + underscoreField = (PropertyFieldDefinition)field; + } + else if (field.getName().equals("fdk:with-dash")) + { + dashField = (PropertyFieldDefinition)field; + } + else if (field.getName().equals("fdk:duplicate")) + { + if (field instanceof PropertyFieldDefinition) + { + duplicatePropField = (PropertyFieldDefinition)field; + } + else if (field instanceof AssociationFieldDefinition) + { + duplicateAssocField = (AssociationFieldDefinition)field; + } + } + } + + assertNotNull("Expected to find nameField", nameField); + assertNotNull("Expected to find textField", textField); + assertNotNull("Expected to find underscoreField", underscoreField); + assertNotNull("Expected to find dashField", dashField); + assertNotNull("Expected to find duplicatePropField", duplicatePropField); + assertNotNull("Expected to find duplicateAssocField", duplicateAssocField); + + // check the field values + FormData values = form.getFormData(); + assertEquals(name, values.getFieldData(nameField.getDataKeyName()).getValue()); + assertEquals(textValue, values.getFieldData(textField.getDataKeyName()).getValue()); + assertEquals(underscoreValue, values.getFieldData(underscoreField.getDataKeyName()).getValue()); + assertEquals(dashValue, values.getFieldData(dashField.getDataKeyName()).getValue()); + assertEquals(duplicateValue, values.getFieldData(duplicatePropField.getDataKeyName()).getValue()); + List assocs = (List)values.getFieldData(duplicateAssocField.getDataKeyName()).getValue(); + assertNotNull(assocs); + assertEquals(0, assocs.size()); + + // update the properties via FormService + FormData data = new FormData(); + + // setup the new property values + String newText = "This is the new text property"; + data.addFieldData(textField.getDataKeyName(), newText); + + String newUnderscore = "This is the new value for the underscore property."; + data.addFieldData(underscoreField.getDataKeyName(), newUnderscore); + + String newDash = "This is the new value for the dash property."; + data.addFieldData(dashField.getDataKeyName(), newDash); + + String newDuplicateProp = "This is the new value for the duplicate property."; + data.addFieldData(duplicatePropField.getDataKeyName(), newDuplicateProp); + + // add new association value + data.addFieldData(duplicateAssocField.getDataKeyName() + "_added", this.document.toString()); + + // persist the data + this.formService.saveForm(new Item(NODE_FORM_ITEM_KIND, everythingNode.toString()), data); + + // retrieve the data directly from the node service to ensure its been changed + Map updatedProps = this.nodeService.getProperties(everythingNode); + String updatedText = (String)updatedProps.get(textProperty); + String updatedUnderscore = (String)updatedProps.get(underscoreProperty); + String updatedDash = (String)updatedProps.get(dashProperty); + String updatedDuplicate = (String)updatedProps.get(duplicateProperty); + + // check values were updated + assertEquals(newText, updatedText); + assertEquals(newUnderscore, updatedUnderscore); + assertEquals(newDash, updatedDash); + assertEquals(newDuplicateProp, updatedDuplicate); + + // retrieve the association that should now be present + assocs = this.nodeService.getTargetAssocs(everythingNode, duplicateProperty); + assertEquals(1, assocs.size()); } public void testNoForm() throws Exception diff --git a/source/java/org/alfresco/repo/forms/processor/node/ContentModelFormProcessor.java b/source/java/org/alfresco/repo/forms/processor/node/ContentModelFormProcessor.java index 4868baf71d..cabb519fda 100644 --- a/source/java/org/alfresco/repo/forms/processor/node/ContentModelFormProcessor.java +++ b/source/java/org/alfresco/repo/forms/processor/node/ContentModelFormProcessor.java @@ -1369,6 +1369,18 @@ public abstract class ContentModelFormProcessor extends else { contentData = (ContentData) this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); + + if (contentData != null) + { + // if the ContentData object already exists in propsToPersist extract the mimetype + // and encoding and set on the ContentData object just retrieved + if (propsToPersist.containsKey(ContentModel.PROP_CONTENT)) + { + ContentData mimetypeEncoding = (ContentData)propsToPersist.get(ContentModel.PROP_CONTENT); + contentData = ContentData.setMimetype(contentData, mimetypeEncoding.getMimetype()); + contentData = ContentData.setEncoding(contentData, mimetypeEncoding.getEncoding()); + } + } } // add the potentially changed content data object back to property map for persistence diff --git a/source/java/org/alfresco/repo/importer/view/ViewParser.java b/source/java/org/alfresco/repo/importer/view/ViewParser.java index 872698c01d..124e52fb2a 100644 --- a/source/java/org/alfresco/repo/importer/view/ViewParser.java +++ b/source/java/org/alfresco/repo/importer/view/ViewParser.java @@ -20,7 +20,6 @@ package org.alfresco.repo.importer.view; import java.io.IOException; import java.io.Reader; -import java.io.Serializable; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -43,6 +42,7 @@ import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.view.ImporterException; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.ISO9075; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.xmlpull.v1.XmlPullParser; @@ -662,6 +662,9 @@ public class ViewParser implements Parser { NodeContext node = peekNodeContext(parserContext.elementStack); + // decode property name + propertyName = QName.createQName(propertyName.getNamespaceURI(), ISO9075.decode(propertyName.getLocalName())); + // Extract single value String value = ""; int eventType = xpp.next(); diff --git a/source/java/org/alfresco/repo/jscript/People.java b/source/java/org/alfresco/repo/jscript/People.java index 12b2dff645..81d3778329 100644 --- a/source/java/org/alfresco/repo/jscript/People.java +++ b/source/java/org/alfresco/repo/jscript/People.java @@ -29,6 +29,7 @@ import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.UserNameGenerator; import org.alfresco.repo.security.authority.AuthorityDAO; +import org.alfresco.repo.security.sync.UserRegistrySynchronizer; import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.NodeRef; @@ -43,6 +44,7 @@ import org.alfresco.service.cmr.security.MutableAuthenticationService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.usage.ContentUsageService; import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; import org.springframework.extensions.surf.util.ParameterCheck; import org.alfresco.util.PropertyMap; import org.alfresco.util.ValueDerivingMapFactory; @@ -72,6 +74,7 @@ public final class People extends BaseScopableProcessorExtension implements Init private ContentUsageService contentUsageService; private TenantService tenantService; private UserNameGenerator usernameGenerator; + private UserRegistrySynchronizer userRegistrySynchronizer; private StoreRef storeRef; private ValueDerivingMapFactory valueDerivingMapFactory; private int numRetries = 10; @@ -207,7 +210,17 @@ public final class People extends BaseScopableProcessorExtension implements Init { this.usernameGenerator = userNameGenerator; } - + + /** + * Set the UserRegistrySynchronizer + * + * @param userRegistrySynchronizer + */ + public void setUserRegistrySynchronizer(UserRegistrySynchronizer userRegistrySynchronizer) + { + this.userRegistrySynchronizer = userRegistrySynchronizer; + } + /** * Delete a Person with the given username * @@ -780,6 +793,26 @@ public final class People extends BaseScopableProcessorExtension implements Init retVal.putAll(this.valueDerivingMapFactory.getMap(person)); return retVal; } + + /** + * Return a map of the Person properties that are marked as immutable for the given user. + * This enables a script to interogate which properties are dealt with by an external + * system such as LDAP and should not be mutable in any client UI. + * + * @param username + * + * @return ScriptableHashMap + */ + public ScriptableHashMap getImmutableProperties(String username) + { + Set props = userRegistrySynchronizer.getPersonMappedProperties(username); + ScriptableHashMap propMap = new ScriptableHashMap(); + for (QName prop : props) + { + propMap.put(prop.toString(), Boolean.TRUE); + } + return propMap; + } /** * Get Contained Authorities 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 e424b080b2..d1b556a6a0 100644 --- a/source/java/org/alfresco/repo/model/ml/tools/EditionServiceImplTest.java +++ b/source/java/org/alfresco/repo/model/ml/tools/EditionServiceImplTest.java @@ -20,8 +20,6 @@ package org.alfresco.repo.model.ml.tools; import java.io.Serializable; import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -29,7 +27,6 @@ import java.util.Map; import org.alfresco.model.ContentModel; import org.alfresco.repo.version.VersionModel; -import org.alfresco.repo.version.common.VersionLabelComparator; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.version.Version; import org.alfresco.service.cmr.version.VersionHistory; @@ -76,7 +73,7 @@ public class EditionServiceImplTest extends AbstractMultilingualTestCases */ pivot = editionService.createEdition(pivot, versionProperties); - editions = orderVersions(editionService.getEditions(mlContainerNodeRef).getAllVersions()); + 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")); @@ -88,7 +85,7 @@ public class EditionServiceImplTest extends AbstractMultilingualTestCases versionProperties = new HashMap(); versionProperties.put(VersionModel.PROP_VERSION_TYPE, VersionType.MAJOR); pivot = editionService.createEdition(pivot, versionProperties); - editions = orderVersions(editionService.getEditions(mlContainerNodeRef).getAllVersions()); + 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")); @@ -100,7 +97,7 @@ public class EditionServiceImplTest extends AbstractMultilingualTestCases versionProperties = new HashMap(); versionProperties.put(VersionModel.PROP_VERSION_TYPE, VersionType.MINOR); pivot = editionService.createEdition(pivot, versionProperties); - editions = orderVersions(editionService.getEditions(mlContainerNodeRef).getAllVersions()); + 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")); @@ -203,13 +200,4 @@ public class EditionServiceImplTest extends AbstractMultilingualTestCases return multilingualContentService.getTranslationContainer(chineseContentNodeRef); } - - @SuppressWarnings("unchecked") - private List orderVersions(Collection allVersions) - { - List versionsAsList = new ArrayList(allVersions.size()); - versionsAsList.addAll(allVersions); - Collections.sort(versionsAsList, new VersionLabelComparator()); - return versionsAsList; - } } diff --git a/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java b/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java index 9c02e6256f..6420990087 100644 --- a/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java +++ b/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java @@ -82,7 +82,6 @@ import org.alfresco.service.cmr.rule.Rule; import org.alfresco.service.cmr.rule.RuleService; import org.alfresco.service.cmr.rule.RuleServiceException; import org.alfresco.service.cmr.rule.RuleType; -import org.alfresco.service.cmr.version.VersionService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; @@ -120,7 +119,6 @@ public class RuleServiceCoverageTest extends TestCase private CopyService copyService; private AuthenticationComponent authenticationComponent; private FileFolderService fileFolderService; - private VersionService versionService; /** * Category related values @@ -161,7 +159,6 @@ public class RuleServiceCoverageTest extends TestCase this.transformerRegistry = (ContentTransformerRegistry)applicationContext.getBean("contentTransformerRegistry"); this.authenticationComponent = (AuthenticationComponent)applicationContext.getBean("authenticationComponent"); this.fileFolderService = serviceRegistry.getFileFolderService(); - this.versionService = serviceRegistry.getVersionService(); //authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); //authenticationComponent.setSystemUserAsCurrentUser(); @@ -333,13 +330,12 @@ public class RuleServiceCoverageTest extends TestCase NodeRef newNodeRef = this.nodeService.createNode( this.nodeRef, - ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, QName.createQName(TEST_NAMESPACE, "children"), ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); + getContentProperties()).getChildRef(); addContentToNode(newNodeRef); - assertTrue(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); - assertEquals(1, versionService.getVersionHistory(newNodeRef).getAllVersions().size()); + assertTrue(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); Map params2 = new HashMap(2); params2.put(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ApplicationModel.ASPECT_SIMPLE_WORKFLOW); @@ -400,6 +396,7 @@ public class RuleServiceCoverageTest extends TestCase ContentModel.TYPE_CONTENT, contentProps).getChildRef(); //addContentToNode(newNodeRef); + nodeService.removeAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE); assertFalse(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); // Use the file folder to change the name of the node @@ -453,13 +450,13 @@ public class RuleServiceCoverageTest extends TestCase this.nodeService.addAspect(this.nodeRef, ContentModel.ASPECT_LOCKABLE, null); Map params = new HashMap(1); - params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); + params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); Rule rule = createRule( - RuleType.INBOUND, - AddFeaturesActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, + RuleType.INBOUND, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, null); this.ruleService.saveRule(this.nodeRef, rule); @@ -467,24 +464,23 @@ public class RuleServiceCoverageTest extends TestCase NodeRef newNodeRef = this.nodeService.createNode( this.nodeRef, - ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, QName.createQName(TEST_NAMESPACE, "children"), ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); + getContentProperties()).getChildRef(); addContentToNode(newNodeRef); - assertFalse(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + assertFalse(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); this.ruleService.enableRule(rule); NodeRef newNodeRef2 = this.nodeService.createNode( this.nodeRef, - ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, QName.createQName(TEST_NAMESPACE, "children"), ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); + getContentProperties()).getChildRef(); addContentToNode(newNodeRef2); - assertTrue(this.nodeService.hasAspect(newNodeRef2, ContentModel.ASPECT_VERSIONABLE)); - assertEquals(1, versionService.getVersionHistory(newNodeRef2).getAllVersions().size()); + assertTrue(this.nodeService.hasAspect(newNodeRef2, ContentModel.ASPECT_VERSIONABLE)); } @@ -672,8 +668,7 @@ public class RuleServiceCoverageTest extends TestCase } }; NodeRef newNodeRef = transactionService.getRetryingTransactionHelper().doInTransaction(callback1); - assertTrue(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); - assertEquals(1, versionService.getVersionHistory(newNodeRef).getAllVersions().size()); + assertTrue(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); // Check rule does not get fired when the node has the incorrect category value RetryingTransactionCallback callback3 = new RetryingTransactionCallback() @@ -1127,7 +1122,8 @@ public class RuleServiceCoverageTest extends TestCase * condition: no-condition() * action: checkin() */ - public void testCheckInAction() + @SuppressWarnings("unchecked") + public void testCheckInAction() { Map params = new HashMap(1); params.put(CheckInActionExecuter.PARAM_DESCRIPTION, "The version description."); @@ -1185,38 +1181,37 @@ public class RuleServiceCoverageTest extends TestCase public void testRulesDisabled() { Map actionParams = new HashMap(1); - actionParams.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); + actionParams.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); Rule rule = createRule( - RuleType.INBOUND, - AddFeaturesActionExecuter.NAME, - actionParams, - NoConditionEvaluator.NAME, - null); + RuleType.INBOUND, + AddFeaturesActionExecuter.NAME, + actionParams, + NoConditionEvaluator.NAME, + null); - this.ruleService.saveRule(this.nodeRef, rule); + this.ruleService.saveRule(this.nodeRef, rule); this.ruleService.disableRules(this.nodeRef); NodeRef newNodeRef = this.nodeService.createNode( this.nodeRef, - ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, QName.createQName(TEST_NAMESPACE, "children"), ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); + getContentProperties()).getChildRef(); addContentToNode(newNodeRef); - assertFalse(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + assertFalse(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); this.ruleService.enableRules(this.nodeRef); NodeRef newNodeRef2 = this.nodeService.createNode( this.nodeRef, - ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, QName.createQName(TEST_NAMESPACE, "children"), ContentModel.TYPE_CONTENT, - getContentProperties()).getChildRef(); + getContentProperties()).getChildRef(); addContentToNode(newNodeRef2); - assertTrue(this.nodeService.hasAspect(newNodeRef2, ContentModel.ASPECT_VERSIONABLE)); - assertEquals(1, versionService.getVersionHistory(newNodeRef2).getAllVersions().size()); + assertTrue(this.nodeService.hasAspect(newNodeRef2, ContentModel.ASPECT_VERSIONABLE)); } /** @@ -1418,90 +1413,12 @@ public class RuleServiceCoverageTest extends TestCase public void testInboundRuleType() { Map params = new HashMap(1); - params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); + params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); Rule rule = createRule( - RuleType.INBOUND, - AddFeaturesActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, - null); - - this.ruleService.saveRule(this.nodeRef, rule); - - // Create a non-content node - NodeRef newNodeRef = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTAINER).getChildRef(); - assertTrue(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); - assertEquals(1, versionService.getVersionHistory(newNodeRef).getAllVersions().size()); - - // Create a content node - NodeRef contentNodeRef = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT).getChildRef(); - assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); - addContentToNode(contentNodeRef); - assertTrue(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); - assertEquals(1, versionService.getVersionHistory(contentNodeRef).getAllVersions().size()); - - // Create a content node in a single txn - RetryingTransactionCallback callback = new RetryingTransactionCallback() - { - public NodeRef execute() throws Throwable - { - NodeRef contentNodeRef = nodeService.createNode( - nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT).getChildRef(); - addContentToNode(contentNodeRef); - return contentNodeRef; - } - }; - NodeRef contentNodeRef2 = transactionService.getRetryingTransactionHelper().doInTransaction(callback); - - assertTrue(this.nodeService.hasAspect(contentNodeRef2, ContentModel.ASPECT_VERSIONABLE)); - assertEquals(1, versionService.getVersionHistory(contentNodeRef2).getAllVersions().size()); - - // Create a node to be moved - NodeRef moveNode = this.nodeService.createNode( - newNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT).getChildRef(); - addContentToNode(moveNode); - assertFalse(this.nodeService.hasAspect(moveNode, ContentModel.ASPECT_VERSIONABLE)); - - this.nodeService.moveNode( - moveNode, - this.nodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(TEST_NAMESPACE, "children")); - assertTrue(this.nodeService.hasAspect(moveNode, ContentModel.ASPECT_VERSIONABLE)); - assertEquals(1, versionService.getVersionHistory(moveNode).getAllVersions().size()); - - // Ensure the rule type does not get fired when the node is updated - this.nodeService.removeAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE); - assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); - this.nodeService.setProperty(contentNodeRef, ContentModel.PROP_NAME, "name.txt"); - assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); - addContentToNode(contentNodeRef); - assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); - } - - public void testUpdateRuleType() - { - Map params = new HashMap(1); - params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); - Rule rule = createRule( - RuleType.UPDATE, - AddFeaturesActionExecuter.NAME, - params, - NoConditionEvaluator.NAME, + RuleType.INBOUND, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, null); this.ruleService.saveRule(this.nodeRef, rule); @@ -1509,44 +1426,99 @@ public class RuleServiceCoverageTest extends TestCase // Create a non-content node NodeRef newNodeRef = this.nodeService.createNode( this.nodeRef, - ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTAINER).getChildRef(); + assertTrue(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // Create a content node + NodeRef contentNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT).getChildRef(); + assertTrue(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + addContentToNode(contentNodeRef); + assertTrue(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // Create a node to be moved + NodeRef moveNode = this.nodeService.createNode( + newNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children"), + ContentModel.TYPE_CONTENT).getChildRef(); + addContentToNode(moveNode); + assertFalse(this.nodeService.hasAspect(moveNode, ContentModel.ASPECT_VERSIONABLE)); + this.nodeService.moveNode( + moveNode, + this.nodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(TEST_NAMESPACE, "children")); + assertTrue(this.nodeService.hasAspect(moveNode, ContentModel.ASPECT_VERSIONABLE)); + + // Enusre the rule type does not get fired when the node is updated + this.nodeService.removeAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE); + assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + this.nodeService.setProperty(contentNodeRef, ContentModel.PROP_NAME, "name.txt"); + assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + addContentToNode(contentNodeRef); + assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + } + + public void testUpdateRuleType() + { + Map params = new HashMap(1); + params.put("aspect-name", ContentModel.ASPECT_VERSIONABLE); + Rule rule = createRule( + RuleType.UPDATE, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + + this.ruleService.saveRule(this.nodeRef, rule); + + // Create a non-content node + NodeRef newNodeRef = this.nodeService.createNode( + this.nodeRef, + ContentModel.ASSOC_CHILDREN, QName.createQName(TEST_NAMESPACE, "children"), ContentModel.TYPE_FOLDER).getChildRef(); + this.nodeService.removeAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE); assertFalse(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); // Update the non-content node this.nodeService.setProperty(newNodeRef, ContentModel.PROP_NAME, "testName"); assertTrue(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); - assertEquals(1, versionService.getVersionHistory(newNodeRef).getAllVersions().size()); // Create a content node NodeRef contentNodeRef = this.nodeService.createNode( this.nodeRef, - ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, QName.createQName(TEST_NAMESPACE, "children"), - ContentModel.TYPE_CONTENT).getChildRef(); + ContentModel.TYPE_CONTENT).getChildRef(); + nodeService.removeAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE); + assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); + addContentToNode(contentNodeRef); assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); - addContentToNode(contentNodeRef); - assertFalse(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); - addContentToNode(contentNodeRef); + addContentToNode(contentNodeRef); assertTrue(this.nodeService.hasAspect(contentNodeRef, ContentModel.ASPECT_VERSIONABLE)); - assertEquals(1, versionService.getVersionHistory(contentNodeRef).getAllVersions().size()); // Create a non content node, setting a property at the same time Map props = new HashMap(1); props.put(ContentModel.PROP_NAME, "testName"); NodeRef nodeRef2 = this.nodeService.createNode( this.nodeRef, - ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, QName.createQName(TEST_NAMESPACE, "children"), ContentModel.TYPE_FOLDER, props).getChildRef(); + nodeService.removeAspect(nodeRef2, ContentModel.ASPECT_VERSIONABLE); assertFalse(this.nodeService.hasAspect(nodeRef2, ContentModel.ASPECT_VERSIONABLE)); this.nodeService.setProperty(nodeRef2, ContentModel.PROP_NAME, "testName"); assertFalse(this.nodeService.hasAspect(nodeRef2, ContentModel.ASPECT_VERSIONABLE)); this.nodeService.setProperty(nodeRef2, ContentModel.PROP_NAME, "testName2"); assertTrue(this.nodeService.hasAspect(nodeRef2, ContentModel.ASPECT_VERSIONABLE)); - assertEquals(1, versionService.getVersionHistory(nodeRef2).getAllVersions().size()); transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { @@ -1554,9 +1526,9 @@ public class RuleServiceCoverageTest extends TestCase { Map props = new HashMap(1); props.put(ContentModel.PROP_NAME, "testName"); - NodeRef nodeRef3 = RuleServiceCoverageTest.this.nodeService.createNode( + NodeRef nodeRef3 = RuleServiceCoverageTest.this.nodeService.createNode( RuleServiceCoverageTest.this.nodeRef, - ContentModel.ASSOC_CHILDREN, + ContentModel.ASSOC_CHILDREN, QName.createQName(TEST_NAMESPACE, "children"), ContentModel.TYPE_FOLDER, props).getChildRef(); @@ -1602,12 +1574,11 @@ public class RuleServiceCoverageTest extends TestCase // Move the node out of the actionable folder this.nodeService.moveNode( - newNodeRef, - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, + newNodeRef, + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, QName.createQName(TEST_NAMESPACE, "children")); assertTrue(this.nodeService.hasAspect(newNodeRef, ContentModel.ASPECT_VERSIONABLE)); - assertEquals(1, versionService.getVersionHistory(newNodeRef).getAllVersions().size()); // Check the deletion of a node @@ -1686,7 +1657,6 @@ public class RuleServiceCoverageTest extends TestCase for (NodeRef ref : nodeRefs) { assertTrue(this.nodeService.hasAspect(ref, ContentModel.ASPECT_VERSIONABLE)); - assertEquals(1, versionService.getVersionHistory(ref).getAllVersions().size()); } System.out.println(sw.prettyPrint()); diff --git a/source/java/org/alfresco/repo/rule/RuleServiceImpl.java b/source/java/org/alfresco/repo/rule/RuleServiceImpl.java index 19c17fdf0e..c4b2b81ded 100644 --- a/source/java/org/alfresco/repo/rule/RuleServiceImpl.java +++ b/source/java/org/alfresco/repo/rule/RuleServiceImpl.java @@ -963,6 +963,34 @@ public class RuleServiceImpl addRulePendingExecution(actionableNodeRef, actionedUponNodeRef, rule, false); } + @SuppressWarnings("unchecked") + public void removeRulePendingExecution(NodeRef actionedUponNodeRef) + { + ParameterCheck.mandatory("actionedUponNodeRef", actionedUponNodeRef); + + List pendingRules = (List) AlfrescoTransactionSupport.getResource(KEY_RULES_PENDING); + if (pendingRules != null) + { + boolean listUpdated = false; + List temp = new ArrayList(pendingRules); + for (PendingRuleData pendingRuleData : temp) + { + if (pendingRuleData.getActionedUponNodeRef().equals(actionedUponNodeRef) == true) + { + // Remove from the pending list + pendingRules.remove(pendingRuleData); + listUpdated = true; + } + } + + if (listUpdated == true) + { + AlfrescoTransactionSupport.bindResource(KEY_RULES_PENDING, pendingRules); + AlfrescoTransactionSupport.bindListener(this.ruleTransactionListener); + } + } + } + /** * @see org.alfresco.repo.rule.RuntimeRuleService#addRulePendingExecution(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.rule.Rule, boolean) */ diff --git a/source/java/org/alfresco/repo/rule/RuntimeRuleService.java b/source/java/org/alfresco/repo/rule/RuntimeRuleService.java index 466321eba7..555f860960 100644 --- a/source/java/org/alfresco/repo/rule/RuntimeRuleService.java +++ b/source/java/org/alfresco/repo/rule/RuntimeRuleService.java @@ -27,19 +27,64 @@ import org.alfresco.service.cmr.rule.Rule; import org.alfresco.service.cmr.rule.RuleType; /** + * Runtime rule service + * * @author Roy Wetherall */ public interface RuntimeRuleService { + /** + * Execute a rule + * + * @param rule rule + * @param actionedUponNodeRef actioned upon node reference + * @param executedRules already executed rules + */ void executeRule(Rule rule, NodeRef actionedUponNodeRef, Set executedRules); + /** + * Add a rule to the pending execution list + * + * @param actionableNodeRef actionable node reference + * @param actionedUponNodeRef actioned upon node reference + * @param rule rule + */ void addRulePendingExecution(NodeRef actionableNodeRef, NodeRef actionedUponNodeRef, Rule rule); + /** + * Add a rule to the pending execution list + * + * @param actionableNodeRef actionable node reference + * @param actionedUponNodeRef actioned upon node reference + * @param rule rule + * @param executeAtEnd true if execute rule at the end of the transaction, false otherwise + */ void addRulePendingExecution(NodeRef actionableNodeRef, NodeRef actionedUponNodeRef, Rule rule, boolean executeAtEnd); + /** + * Remove all pending rules that are actioning upon the given node reference + * + * @param actionedUponNodeRef actioned upon node reference + */ + public void removeRulePendingExecution(NodeRef actionedUponNodeRef); + + /** + * Execute all pending rules + */ void executePendingRules(); + /** + * Register a rule type + * + * @param ruleType rule type + */ void registerRuleType(RuleType ruleType); + /** + * Get the folder that the rules are saved within for a given actionable node + * + * @param nodeRef node reference + * @return ChildAssocationref child association reference to the rule folder + */ ChildAssociationRef getSavedRuleFolderAssoc(NodeRef nodeRef); } diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/CreateNodeRuleTrigger.java b/source/java/org/alfresco/repo/rule/ruletrigger/CreateNodeRuleTrigger.java index 223f6714d9..35be108c85 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/CreateNodeRuleTrigger.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/CreateNodeRuleTrigger.java @@ -21,11 +21,7 @@ package org.alfresco.repo.rule.ruletrigger; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.Behaviour.NotificationFrequency; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport; -import org.alfresco.service.cmr.dictionary.ClassDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.repo.rule.RuntimeRuleService; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.NamespaceService; @@ -57,20 +53,34 @@ public class CreateNodeRuleTrigger extends RuleTriggerAbstractBase private static final String POLICY = "onCreateNode"; + private static final QName ASPECT_NO_CONTENT = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "noContent"); + + /** Indicates whether this is a class behaviour or not */ private boolean isClassBehaviour = false; - public void setIsClassBehaviour(boolean isClassBehaviour) - { - this.isClassBehaviour = isClassBehaviour; - } + /** Runtime rule service */ + RuntimeRuleService ruleService; - DictionaryService dictionaryService; - - public void setDictionaryService(DictionaryService dictionaryService) + /** + * Set whether this is a class behaviour or not + * + * @param isClassBehaviour + */ + public void setIsClassBehaviour(boolean isClassBehaviour) { - this.dictionaryService = dictionaryService; + this.isClassBehaviour = isClassBehaviour; } + /** + * Set the rule service + * + * @param ruleService rule service + */ + public void setRuleService(RuntimeRuleService ruleService) + { + this.ruleService = ruleService; + } + /** * @see org.alfresco.repo.rule.ruletrigger.RuleTrigger#registerRuleTrigger() */ @@ -89,89 +99,95 @@ public class CreateNodeRuleTrigger extends RuleTriggerAbstractBase QName.createQName(NamespaceService.ALFRESCO_URI, POLICY), this, new JavaBehaviour(this, POLICY)); - } + } - // Register interest in the addition of the inline editable aspect at the end of the transaction - // NOTE: this work around is nessesary because we can't fire the rules directly since CIFS is not transactional - policyComponent.bindClassBehaviour( + // Register interest in the addition and removal of the sys:noContent aspect + this.policyComponent.bindClassBehaviour( NodeServicePolicies.OnAddAspectPolicy.QNAME, - this, - new JavaBehaviour(this, "onAddAspect", NotificationFrequency.TRANSACTION_COMMIT)); - - } - - // NOTE: this work around is nessesary because we can't fire the rules directly since CIFS is not transactional - public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName) - { - if (nodeService.exists(nodeRef) == true) - { - // See if we have created the node in this transaction - if (AlfrescoTransactionSupport.getResource(nodeRef.toString()) != null) - { - Boolean value = (Boolean)nodeService.getProperty(nodeRef, QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "editInline")); - if (value != null) - { - boolean editInline = value.booleanValue(); - if (editInline == true) - { - // Then we should be triggering the rules - NodeRef parentNodeRef = nodeService.getPrimaryParent(nodeRef).getParentRef(); - triggerRulesImpl(parentNodeRef, nodeRef); - } - } - } - } + ASPECT_NO_CONTENT, + new JavaBehaviour(this, "onAddAspect", NotificationFrequency.FIRST_EVENT)); + this.policyComponent.bindClassBehaviour( + NodeServicePolicies.OnRemoveAspectPolicy.QNAME, + ASPECT_NO_CONTENT, + new JavaBehaviour(this, "onRemoveAspect", NotificationFrequency.FIRST_EVENT)); } /** * {@inheritDoc} */ public void onCreateNode(ChildAssociationRef childAssocRef) - { - // Only fire the rule if the node is question has no potential to contain content - // TODO we need to find a better way to do this .. how can this be resolved in CIFS?? - boolean triggerRule = false; - NodeRef nodeRef = childAssocRef.getChildRef(); - QName type = this.nodeService.getType(nodeRef); - ClassDefinition classDefinition = this.dictionaryService.getClass(type); - if (classDefinition != null) + { + if (childAssocRef != null) { - for (PropertyDefinition propertyDefinition : classDefinition.getProperties().values()) + NodeRef nodeRef = childAssocRef.getChildRef(); + if (nodeRef != null && + nodeService.exists(nodeRef) == true && + nodeService.hasAspect(nodeRef, ASPECT_NO_CONTENT) == false) { - if (propertyDefinition.getDataType().getName().equals(DataTypeDefinition.CONTENT) == true) + NodeRef parentNodeRef = childAssocRef.getParentRef(); + + if (logger.isDebugEnabled() == true) { - triggerRule = true; - break; + logger.debug( + "Create node rule trigger fired for parent node " + + this.nodeService.getType(parentNodeRef).toString() + " " + parentNodeRef + + " and child node " + + this.nodeService.getType(nodeRef).toString() + " " + nodeRef); } + + triggerRules(parentNodeRef, nodeRef); } } - - if (triggerRule == false) + } + + /** + * On add aspect behaviour + * + * @param nodeRef + * @param aspectTypeQName + */ + public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName) + { + if (nodeService.exists(nodeRef) == true && nodeService.hasAspect(nodeRef, ASPECT_NO_CONTENT) == true) { - triggerRulesImpl(childAssocRef.getParentRef(), childAssocRef.getChildRef()); + if (logger.isDebugEnabled() == true) + { + logger.debug( + "Removing the pending rules for the node " + + nodeRef.toString() + + " since the noContent aspect has been applied."); + } + + // Removes any rules that have already been triggered for that node + ruleService.removeRulePendingExecution(nodeRef); } - - // Regardless of whether the rule is triggered, mark this transaction as having created this node - AlfrescoTransactionSupport.bindResource(childAssocRef.getChildRef().toString(), childAssocRef.getChildRef().toString()); } /** - * Trigger the rules with some log + * On remove aspect behaviour * - * @param parentNodeRef - * @param childNodeRef + * @param nodeRef + * @param aspectTypeQName */ - private void triggerRulesImpl(NodeRef parentNodeRef, NodeRef childNodeRef) + public void onRemoveAspect(NodeRef nodeRef, QName aspectTypeQName) { - if (logger.isDebugEnabled() == true) + if (nodeService.exists(nodeRef) == true && nodeService.hasAspect(nodeRef, ASPECT_NO_CONTENT) == false) { - logger.debug( - "Create node rule trigger fired for parent node " + - this.nodeService.getType(parentNodeRef).toString() + " " + parentNodeRef + - " and child node " + - this.nodeService.getType(childNodeRef).toString() + " " + childNodeRef); + // We can assume it is the primary parent since it is only in the CIFS use case this aspect + // is added. It's added during create, therefore we must be talking about the primary parent + NodeRef parentNodeRef = nodeService.getPrimaryParent(nodeRef).getParentRef(); + + if (logger.isDebugEnabled() == true) + { + logger.debug( + "Create node rule trigger fired for parent node " + + this.nodeService.getType(parentNodeRef).toString() + " " + parentNodeRef + + " and child node " + + this.nodeService.getType(nodeRef).toString() + " " + nodeRef + + " (this was triggered on removal of the noContent aspect)"); + } + + triggerRules(parentNodeRef, nodeRef); } - - triggerRules(parentNodeRef, childNodeRef); } } 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 7fdfa6d3fc..a7f0319354 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java @@ -527,7 +527,7 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp } - private class Pair + private static class Pair { private F first; @@ -1609,12 +1609,15 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp return done; } catch (LuceneIndexException e) + { + return 0; + } + finally { if (writer != null) { closeDeltaWriter(); } - return 0; } } else diff --git a/source/java/org/alfresco/repo/search/impl/lucene/fts/FullTextSearchIndexerImpl.java b/source/java/org/alfresco/repo/search/impl/lucene/fts/FullTextSearchIndexerImpl.java index 49dfd285d9..05e4ed7972 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/fts/FullTextSearchIndexerImpl.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/fts/FullTextSearchIndexerImpl.java @@ -26,7 +26,10 @@ import org.alfresco.repo.search.BackgroundIndexerAware; import org.alfresco.repo.search.Indexer; import org.alfresco.repo.search.IndexerAndSearcher; import org.alfresco.repo.search.SupportsBackgroundIndexing; +import org.alfresco.repo.search.impl.lucene.index.IndexInfo; import org.alfresco.service.cmr.repository.StoreRef; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ListableBeanFactory; @@ -40,6 +43,8 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; */ public class FullTextSearchIndexerImpl implements FTSIndexerAware, FullTextSearchIndexer { + private static Log s_logger = LogFactory.getLog(FullTextSearchIndexerImpl.class); + private static Set requiresIndex = new LinkedHashSet(); private static Set indexing = new HashSet(); @@ -50,6 +55,8 @@ public class FullTextSearchIndexerImpl implements FTSIndexerAware, FullTextSearc private boolean paused = false; + private int batchSize = 1000; + /** * */ @@ -66,6 +73,10 @@ public class FullTextSearchIndexerImpl implements FTSIndexerAware, FullTextSearc */ public synchronized void requiresIndex(StoreRef storeRef) { + if(s_logger.isDebugEnabled()) + { + s_logger.debug("FTS index request for "+storeRef); + } requiresIndex.add(storeRef); } @@ -78,6 +89,10 @@ public class FullTextSearchIndexerImpl implements FTSIndexerAware, FullTextSearc { try { + if(s_logger.isDebugEnabled()) + { + s_logger.debug("FTS index completed for "+storeRef+" ... "+remaining+ " remaining"); + } indexing.remove(storeRef); if ((remaining > 0) || (e != null)) { @@ -90,7 +105,6 @@ public class FullTextSearchIndexerImpl implements FTSIndexerAware, FullTextSearc } finally { - // System.out.println("..Index Complete: id is "+this); this.notifyAll(); } } @@ -103,10 +117,16 @@ public class FullTextSearchIndexerImpl implements FTSIndexerAware, FullTextSearc public synchronized void pause() throws InterruptedException { pauseCount++; - // System.out.println("..Waiting "+pauseCount+" id is "+this); + if(s_logger.isTraceEnabled()) + { + s_logger.trace("..Waiting "+pauseCount+" id is "+this); + } while ((indexing.size() > 0)) { - // System.out.println("Pause: Waiting with count of "+indexing.size()+" id is "+this); + if(s_logger.isTraceEnabled()) + { + s_logger.trace("Pause: Waiting with count of "+indexing.size()+" id is "+this); + } this.wait(); } pauseCount--; @@ -115,7 +135,10 @@ public class FullTextSearchIndexerImpl implements FTSIndexerAware, FullTextSearc paused = true; this.notifyAll(); // only resumers } - // System.out.println("..Remaining "+pauseCount +" paused = "+paused+" id is "+this); + if(s_logger.isTraceEnabled()) + { + s_logger.trace("..Remaining "+pauseCount +" paused = "+paused+" id is "+this); + } } /* @@ -127,14 +150,20 @@ public class FullTextSearchIndexerImpl implements FTSIndexerAware, FullTextSearc { if (pauseCount == 0) { - // System.out.println("Direct resume"+" id is "+this); + if(s_logger.isTraceEnabled()) + { + s_logger.trace("Direct resume"+" id is "+this); + } paused = false; } else { while (pauseCount > 0) { - // System.out.println("Reusme waiting on "+pauseCount+" id is "+this); + if(s_logger.isTraceEnabled()) + { + s_logger.trace("Resume waiting on "+pauseCount+" id is "+this); + } this.wait(); } paused = false; @@ -174,19 +203,36 @@ public class FullTextSearchIndexerImpl implements FTSIndexerAware, FullTextSearc StoreRef toIndex = getNextRef(); if (toIndex != null) { - // System.out.println("Indexing "+toIndex+" at "+(new java.util.Date())); + if(s_logger.isDebugEnabled()) + { + s_logger.debug("FTS Indexing "+toIndex+" at "+(new java.util.Date())); + } Indexer indexer = indexerAndSearcherFactory.getIndexer(toIndex); if(indexer instanceof BackgroundIndexerAware) { BackgroundIndexerAware backgroundIndexerAware = (BackgroundIndexerAware)indexer; backgroundIndexerAware.registerCallBack(this); - done += backgroundIndexerAware.updateFullTextSearch(1000); + try + { + done += backgroundIndexerAware.updateFullTextSearch(batchSize); + } + catch (Exception ex) + { + if(s_logger.isWarnEnabled()) + { + s_logger.warn("FTS Job threw exception", ex); + } + done = 1; // better luck next time + } } } else { + if(s_logger.isTraceEnabled()) + { + s_logger.trace("Nothing to FTS index at "+(new java.util.Date())); + } break; - // System.out.println("Nothing to Indexing at "+(new java.util.Date())); } } } @@ -195,7 +241,10 @@ public class FullTextSearchIndexerImpl implements FTSIndexerAware, FullTextSearc { if (paused || (pauseCount > 0)) { - // System.out.println("Indexing suspended"+" id is "+this); + if(s_logger.isTraceEnabled()) + { + s_logger.trace("Indexing suspended - no store available - id is "+this); + } return null; } @@ -253,4 +302,15 @@ public class FullTextSearchIndexerImpl implements FTSIndexerAware, FullTextSearc } } + + /** + * The maximum maximum batch size + * @param batchSize the batchSize to set + */ + public void setBatchSize(int batchSzie) + { + this.batchSize = batchSzie; + } + + } diff --git a/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryVoter.java b/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryVoter.java index 781fb490ed..b59c3407c3 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryVoter.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryVoter.java @@ -405,21 +405,27 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean // now we know the node - we can abstain for certain types and aspects (eg. RM) if(abstainForClassQNames.size() > 0) { - // check node exists (note: for AVM deleted nodes, will skip the abstain check, since exists/getType is accessed via AVMNodeService) - if (nodeService.exists(testNodeRef)) + // For AVM we can not get type and aspect without going through internal AVM security checks + // AVM is never excluded + if(!testNodeRef.getStoreRef().getProtocol().equals(StoreRef.PROTOCOL_AVM)) { - QName typeQName = nodeService.getType(testNodeRef); - if(abstainForClassQNames.contains(typeQName)) + // check node exists (note: for AVM deleted nodes, will skip the abstain check, since exists/getType is accessed via AVMNodeService) + if (nodeService.exists(testNodeRef)) { - return AccessDecisionVoter.ACCESS_ABSTAIN; - } - Set aspectQNames = nodeService.getAspects(testNodeRef); - for(QName abstain : abstainForClassQNames) - { - if(aspectQNames.contains(abstain)) + QName typeQName = nodeService.getType(testNodeRef); + if(abstainForClassQNames.contains(typeQName)) { return AccessDecisionVoter.ACCESS_ABSTAIN; } + + Set aspectQNames = nodeService.getAspects(testNodeRef); + for(QName abstain : abstainForClassQNames) + { + if(aspectQNames.contains(abstain)) + { + return AccessDecisionVoter.ACCESS_ABSTAIN; + } + } } } } diff --git a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java index a925b7367a..fbb0f1d842 100644 --- a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java +++ b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java @@ -22,7 +22,6 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; @@ -868,6 +867,10 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per public void beforeDeleteNode(NodeRef nodeRef) { String username = (String) this.nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME); + if (this.authorityService.isGuestAuthority(username)) + { + throw new AlfrescoRuntimeException("The " + username + " user cannot be deleted."); + } this.personCache.remove(username.toLowerCase()); } diff --git a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java index bd551fe460..408bd5ac93 100644 --- a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java +++ b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java @@ -438,7 +438,47 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl /* * (non-Javadoc) - * @see org.alfresco.repo.security.sync.UserRegistrySynchronizer#ensureExists(java.lang.String) + * @see org.alfresco.repo.security.sync.UserRegistrySynchronizer#getPersonMappedProperties(java.lang.String) + */ + public Set getPersonMappedProperties(String username) + { + Set authorityZones = this.authorityService.getAuthorityZones(username); + if (authorityZones == null) + { + return Collections.emptySet(); + } + Collection instanceIds = this.applicationContextManager.getInstanceIds(); + + // Visit the user registries in priority order and return the person mapping of the first registry that matches + // one of the person's zones + for (String id : instanceIds) + { + String zoneId = AuthorityService.ZONE_AUTH_EXT_PREFIX + id; + if (!authorityZones.contains(zoneId)) + { + continue; + } + ApplicationContext context = this.applicationContextManager.getApplicationContext(id); + try + { + UserRegistry plugin = (UserRegistry) context.getBean(this.sourceBeanName); + if (!(plugin instanceof ActivateableBean) || ((ActivateableBean) plugin).isActive()) + { + return plugin.getPersonMappedProperties(); + } + } + catch (NoSuchBeanDefinitionException e) + { + // Ignore and continue + } + } + + return Collections.emptySet(); + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.security.sync.UserRegistrySynchronizer#createMissingPerson(java.lang.String) */ public boolean createMissingPerson(String userName) { @@ -666,7 +706,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl String groupDisplayName = (String) groupProperties.get(ContentModel.PROP_AUTHORITY_DISPLAY_NAME); if (groupDisplayName == null) { - groupDisplayName = groupName; + groupDisplayName = ChainingUserRegistrySynchronizer.this.authorityService.getShortName(groupName); } // Update the display name now ChainingUserRegistrySynchronizer.this.authorityService.setAuthorityDisplayName(groupName, @@ -694,7 +734,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl String groupDisplayName = (String) groupProperties.get(ContentModel.PROP_AUTHORITY_DISPLAY_NAME); if (groupDisplayName == null) { - groupDisplayName = groupName; + groupDisplayName = ChainingUserRegistrySynchronizer.this.authorityService.getShortName(groupName); } synchronized (this) diff --git a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java index d7c8fff111..2d3db809e9 100644 --- a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java +++ b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java @@ -753,6 +753,18 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase { return filterNodeDescriptions(this.persons, modifiedSince); } + + /* (non-Javadoc) + * @see org.alfresco.repo.security.sync.UserRegistry#getPersonMappedProperties() + */ + public Set getPersonMappedProperties() + { + return new HashSet(Arrays.asList(new QName[] + { + ContentModel.PROP_USERNAME, ContentModel.PROP_FIRSTNAME, ContentModel.PROP_LASTNAME, + ContentModel.PROP_EMAIL, ContentModel.PROP_ORGID, ContentModel.PROP_HOME_FOLDER_PROVIDER + })); + } } /** diff --git a/source/java/org/alfresco/repo/security/sync/UserRegistry.java b/source/java/org/alfresco/repo/security/sync/UserRegistry.java index b4779296f6..984dc9b9d9 100644 --- a/source/java/org/alfresco/repo/security/sync/UserRegistry.java +++ b/source/java/org/alfresco/repo/security/sync/UserRegistry.java @@ -22,6 +22,8 @@ import java.util.Collection; import java.util.Date; import java.util.Set; +import org.alfresco.service.namespace.QName; + /** * A UserRegistry is an encapsulation of an external registry from which user and group information can be * queried (typically an LDAP directory). Implementations may optional support the ability to query only those users and @@ -63,4 +65,12 @@ public interface UserRegistry * the candidate authorities for deletion */ public void processDeletions(final Set candidateAuthoritiesForDeletion); + + /** + * Gets the set of property names that are auto-mapped by this user registry. These should remain read-only for this + * registry's users in the UI. + * + * @return the person mapped properties + */ + public Set getPersonMappedProperties(); } diff --git a/source/java/org/alfresco/repo/security/sync/UserRegistrySynchronizer.java b/source/java/org/alfresco/repo/security/sync/UserRegistrySynchronizer.java index 9228674ead..3944df3514 100644 --- a/source/java/org/alfresco/repo/security/sync/UserRegistrySynchronizer.java +++ b/source/java/org/alfresco/repo/security/sync/UserRegistrySynchronizer.java @@ -18,6 +18,10 @@ */ package org.alfresco.repo.security.sync; +import java.util.Set; + +import org.alfresco.service.namespace.QName; + /** * A UserRegistrySynchronizer is responsible for synchronizing Alfresco's local user (person) and group * (authority) information with one or more external sources (most typically LDAP directories). @@ -59,4 +63,13 @@ public interface UserRegistrySynchronizer * calling synchronously (e.g. in response to an authentication event in the same transaction). */ public void synchronize(boolean forceUpdate, boolean allowDeletions, boolean splitTxns); + + /** + * Gets the set of property names that are auto-mapped for the user with the given user name. These should remain + * read-only for the user in the UI. + * + * @return the person mapped properties + */ + public Set getPersonMappedProperties(String username); + } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java b/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java index b1aaf1f8b1..94786fb0c9 100644 --- a/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java +++ b/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java @@ -60,6 +60,7 @@ import org.alfresco.repo.security.sync.NodeDescription; import org.alfresco.repo.security.sync.UserRegistry; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; import org.alfresco.util.PropertyMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -165,11 +166,11 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial /** Should we error on missing user IDs?. */ private boolean errorOnMissingUID = false; - /** An array of all LDAP attributes to be queried from users. */ - private String[] userAttributeNames; + /** An array of all LDAP attributes to be queried from users plus a set of property QNames. */ + private Pair> userKeys; - /** An array of all LDAP attributes to be queried from groups. */ - private String[] groupAttributeNames; + /** An array of all LDAP attributes to be queried from groups plus a set of property QNames. */ + private Pair> groupKeys; /** The LDAP generalized time format. */ private DateFormat timestampFormat; @@ -509,8 +510,8 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial } this.personAttributeMapping.put(ContentModel.PROP_USERNAME.toPrefixString(this.namespaceService), this.userIdAttributeName); - this.userAttributeNames = getAttributeNames(this.personAttributeMapping); - + this.userKeys = initKeys(this.personAttributeMapping); + // Include a range restriction for the multi-valued member attribute if this is enabled if (this.groupAttributeMapping == null) { @@ -518,11 +519,20 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial } this.groupAttributeMapping.put(ContentModel.PROP_AUTHORITY_NAME.toPrefixString(this.namespaceService), this.groupIdAttributeName); - this.groupAttributeNames = getAttributeNames(this.groupAttributeMapping, + this.groupKeys = initKeys(this.groupAttributeMapping, this.attributeBatchSize > 0 ? this.memberAttributeName + ";range=0-" + (this.attributeBatchSize - 1) : this.memberAttributeName); } + + /* (non-Javadoc) + * @see org.alfresco.repo.security.sync.UserRegistry#getPersonMappedProperties() + */ + public Set getPersonMappedProperties() + { + return this.userKeys.getSecond(); + } + /* * (non-Javadoc) * @see org.alfresco.repo.security.sync.UserRegistry#getPersons(java.util.Date) @@ -619,8 +629,8 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial final LdapName userDistinguishedNamePrefix; try { - groupDistinguishedNamePrefix = new LdapName(this.groupSearchBase.toLowerCase()); - userDistinguishedNamePrefix = new LdapName(this.userSearchBase.toLowerCase()); + groupDistinguishedNamePrefix = fixedLdapName(this.groupSearchBase.toLowerCase()); + userDistinguishedNamePrefix = fixedLdapName(this.userSearchBase.toLowerCase()); } catch (InvalidNameException e) { @@ -707,14 +717,14 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial { // Attempt to parse the member attribute as a DN. If this fails we have a fallback // in the catch block - LdapName distinguishedNameForComparison = new LdapName(attribute.toLowerCase()); + LdapName distinguishedNameForComparison = fixedLdapName(attribute.toLowerCase()); Attribute nameAttribute; // If the user and group search bases are different we may be able to recognize user // and group DNs without a secondary lookup if (disjoint) { - LdapName distinguishedName = new LdapName(attribute); + LdapName distinguishedName = fixedLdapName(attribute); Attributes nameAttributes = distinguishedName.getRdn(distinguishedName.size() - 1) .toAttributes(); @@ -853,7 +863,7 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial { this.ctx.close(); } - }, this.groupSearchBase, query, this.groupAttributeNames); + }, this.groupSearchBase, query, this.groupKeys.getFirst()); if (LDAPUserRegistry.logger.isDebugEnabled()) { @@ -907,8 +917,10 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial } } - private String[] getAttributeNames(Map attributeMapping, String... extraAttibutes) + private Pair> initKeys(Map attributeMapping, + String... extraAttibutes) { + // Compile a complete array of LDAP attribute names, including operational attributes Set attributeSet = new TreeSet(); attributeSet.addAll(Arrays.asList(extraAttibutes)); attributeSet.add(this.modifyTimestampAttributeName); @@ -921,7 +933,15 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial } String[] attributeNames = new String[attributeSet.size()]; attributeSet.toArray(attributeNames); - return attributeNames; + + // Create a set with the property names converted to QNames + Set qnames = new HashSet(attributeMapping.size() * 2); + for (String property : attributeMapping.keySet()) + { + qnames.add(QName.createQName(property, this.namespaceService)); + } + + return new Pair>(attributeNames, qnames); } private NodeDescription mapToNode(Map attributeMapping, Map attributeDefaults, @@ -1000,6 +1020,68 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial n.add(dn); return n; } + + /** + * Works around a bug in the JDK DN parsing. If an RDN has trailing escaped whitespace in the format "\\20" then + * LdapName would normally strip this. This method works around this by replacing "\\20" with "\\ " and "\\0D" with + * "\\\r". + * + * @param dn + * the DN + * @return the parsed ldap name + * @throws InvalidNameException + * if the DN is invalid + */ + private static LdapName fixedLdapName(String dn) throws InvalidNameException + { + // Optimization for DNs without escapes in them + if (dn.indexOf('\\') == -1) + { + return new LdapName(dn); + } + + StringBuilder fixed = new StringBuilder(dn.length()); + int length = dn.length(); + for (int i = 0; i < length; i++) + { + char c = dn.charAt(i); + char c1, c2; + if (c == '\\') + { + if (i + 2 < length && Character.isLetterOrDigit(c1 = dn.charAt(i + 1)) + && Character.isLetterOrDigit(c2 = dn.charAt(i + 2))) + { + if (c1 == '2' && c2 == '0') + { + fixed.append("\\ "); + } + else if (c1 == '0' && c2 == 'D') + { + fixed.append("\\\r"); + } + else + { + fixed.append(dn, i, i + 3); + } + i += 2; + } + else if (i + 1 < length) + { + fixed.append(dn, i, i + 2); + i += 1; + } + else + { + fixed.append(c); + } + } + else + { + fixed.append(c); + } + } + return new LdapName(fixed.toString()); + } /** * Invokes the given callback on each entry returned by the given query. @@ -1257,7 +1339,7 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial this.userSearchCtls = new SearchControls(); this.userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); - this.userSearchCtls.setReturningAttributes(LDAPUserRegistry.this.userAttributeNames); + this.userSearchCtls.setReturningAttributes(LDAPUserRegistry.this.userKeys.getFirst()); this.next = fetchNext(); } diff --git a/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java b/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java index 6885f21afd..a6205c7e87 100644 --- a/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java +++ b/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java @@ -198,6 +198,10 @@ public class RetryingTransactionHelper public void setRetryWaitIncrementMs(int retryWaitIncrementMs) { + if (retryWaitIncrementMs <= 0) + { + throw new IllegalArgumentException("'retryWaitIncrementMs' must be a positive integer."); + } this.retryWaitIncrementMs = retryWaitIncrementMs; } diff --git a/source/java/org/alfresco/repo/transfer/HttpClientTransmitterImpl.java b/source/java/org/alfresco/repo/transfer/HttpClientTransmitterImpl.java index 48a438e01a..3c2594dcc5 100644 --- a/source/java/org/alfresco/repo/transfer/HttpClientTransmitterImpl.java +++ b/source/java/org/alfresco/repo/transfer/HttpClientTransmitterImpl.java @@ -184,7 +184,7 @@ public class HttpClientTransmitterImpl implements TransferTransmitter HttpState httpState = new HttpState(); httpState.setCredentials(new AuthScope(target.getEndpointHost(), target.getEndpointPort(), AuthScope.ANY_REALM), - new UsernamePasswordCredentials(target.getUsername(), new String(target.getUsername()))); + new UsernamePasswordCredentials(target.getUsername(), new String(target.getPassword()))); return httpState; } diff --git a/source/java/org/alfresco/repo/version/MigrationCleanupJob.java b/source/java/org/alfresco/repo/version/MigrationCleanupJob.java index d2ce98cb1b..aebf4ad448 100644 --- a/source/java/org/alfresco/repo/version/MigrationCleanupJob.java +++ b/source/java/org/alfresco/repo/version/MigrationCleanupJob.java @@ -44,16 +44,42 @@ public class MigrationCleanupJob implements Job private static final String KEY_BATCH_SIZE = "batchSize"; private static final String KEY_THREAD_COUNT = "threadCount"; private static final String KEY_ONLY_USE_DEPRECATED_V1 = "onlyUseDeprecatedV1"; + private static final String KEY_MIGRATE_RUN_AS_JOB = "migrateRunAsScheduledJob"; private int batchSize = 1; private int threadCount = 2; private boolean useDeprecatedV1 = false; + private boolean migrateRunAsJob = false; public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap jobData = context.getJobDetail().getJobDataMap(); + String migrateRunAsJobStr = (String)jobData.get(KEY_MIGRATE_RUN_AS_JOB); + if (migrateRunAsJobStr != null) + { + try + { + migrateRunAsJob = new Boolean(migrateRunAsJobStr); + } + catch (Exception e) + { + logger.warn("Invalid 'migrateRunAsJob' value, using default: " + migrateRunAsJob, e); + } + } + + if (migrateRunAsJob) + { + // skip cleanup + if (logger.isDebugEnabled()) + { + logger.debug("Skipping migration cleanup since migration is running as a job (which walso performs the delete)"); + } + + return; + } + String onlyUseDeprecatedV1Str = (String)jobData.get(KEY_ONLY_USE_DEPRECATED_V1); if (onlyUseDeprecatedV1Str != null) { @@ -67,6 +93,7 @@ public class MigrationCleanupJob implements Job } } + if (useDeprecatedV1) { // skip cleanup @@ -126,6 +153,12 @@ public class MigrationCleanupJob implements Job throw new AlfrescoRuntimeException(errorMessage); } + if (AuthenticationUtil.getRunAsUser() == null) + { + logger.info("Set system user"); + AuthenticationUtil.setRunAsUser(AuthenticationUtil.getSystemUserName()); + } + // perform the cleanup of the old version store migrationCleanup.executeCleanup(batchSize, threadCount); diff --git a/source/java/org/alfresco/repo/version/Version2ServiceImpl.java b/source/java/org/alfresco/repo/version/Version2ServiceImpl.java index 4c1a532db0..9f38d5c76e 100644 --- a/source/java/org/alfresco/repo/version/Version2ServiceImpl.java +++ b/source/java/org/alfresco/repo/version/Version2ServiceImpl.java @@ -65,6 +65,9 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe private PermissionService permissionService; + private VersionServiceImpl version1Service = new VersionServiceImpl(); + private VersionMigrator versionMigrator; + private static Comparator versionComparatorAsc = new VersionComparatorAsc(); public void setPermissionService(PermissionService permissionService) @@ -72,6 +75,11 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe this.permissionService = permissionService; } + public void setVersionMigrator(VersionMigrator versionMigrator) + { + this.versionMigrator = versionMigrator; + } + public void setOnlyUseDeprecatedV1(boolean useDeprecatedV1) { this.useDeprecatedV1 = useDeprecatedV1; @@ -84,11 +92,16 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe public void initialise() { super.initialise(); - + if (useDeprecatedV1) { logger.warn("version.store.onlyUseDeprecatedV1=true - using deprecated 'lightWeightVersionStore' by default (not 'version2Store')"); } + else + { + version1Service.setNodeService(dbNodeService); + version1Service.setDbNodeService(dbNodeService); + } } @@ -168,7 +181,7 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe return result; } - + protected Version createVersion( NodeRef nodeRef, Map origVersionProperties, @@ -194,10 +207,10 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe { this.nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, null); } - + // Call the policy behaviour invokeBeforeCreateVersion(nodeRef); - + // version "description" property is added as a standard version property (if not null) rather than a metadata version property String versionDescription = (String)versionProperties.get(Version.PROP_DESCRIPTION); versionProperties.remove(Version.PROP_DESCRIPTION); @@ -207,11 +220,44 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe // Check that the supplied additional version properties do not clash with the reserved ones VersionUtil.checkVersionPropertyNames(versionProperties.keySet()); - + // Check the repository for the version history for this node NodeRef versionHistoryRef = getVersionHistoryNodeRef(nodeRef); NodeRef currentVersionRef = null; - + + if (versionHistoryRef == null) + { + // check for lazy migration + if (! versionMigrator.isMigrationComplete()) + { + NodeRef oldVHRef = version1Service.getVersionHistoryNodeRef(nodeRef); + if (oldVHRef != null) + { + if (logger.isDebugEnabled()) + { + logger.debug("Lazily migrate old version history (background migration in progress): "+oldVHRef); + } + + try + { + versionMigrator.migrateVersion(oldVHRef, true); + } + catch (Throwable t) + { + throw new AlfrescoRuntimeException("Failed to lazily migrate old version history: "+oldVHRef, t); + } + + // should now be able to get new version history + versionHistoryRef = getVersionHistoryNodeRef(nodeRef); + + if (versionHistoryRef == null) + { + throw new AlfrescoRuntimeException("Failed to find lazily migrated version history for node: "+nodeRef); + } + } + } + } + if (versionHistoryRef == null) { // Create the version history @@ -302,6 +348,8 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe */ protected NodeRef createVersionHistory(NodeRef nodeRef) { + long start = System.currentTimeMillis(); + HashMap props = new HashMap(); props.put(ContentModel.PROP_NAME, nodeRef.getId()); props.put(Version2Model.PROP_QNAME_VERSIONED_NODE_ID, nodeRef.getId()); @@ -316,7 +364,7 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe if (logger.isTraceEnabled()) { - logger.trace("created version history nodeRef: " + childAssocRef.getChildRef() + " for " + nodeRef); + logger.trace("created version history nodeRef: " + childAssocRef.getChildRef() + " for " + nodeRef + " in "+(System.currentTimeMillis()-start)+" ms"); } return childAssocRef.getChildRef(); @@ -333,14 +381,31 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe } VersionHistory versionHistory = null; - + // Get the version history regardless of whether the node is still 'live' or not NodeRef versionHistoryRef = getVersionHistoryNodeRef(nodeRef); if (versionHistoryRef != null) { versionHistory = buildVersionHistory(versionHistoryRef, nodeRef); } - + else + { + // to allow (optional) lazy migration + if (! versionMigrator.isMigrationComplete()) + { + NodeRef oldVHRef = version1Service.getVersionHistoryNodeRef(nodeRef); + if (oldVHRef != null) + { + if (logger.isDebugEnabled()) + { + logger.debug("Get old version history (background migration in progress): "+oldVHRef); + } + + versionHistory = version1Service.getVersionHistory(nodeRef); + } + } + } + return versionHistory; } diff --git a/source/java/org/alfresco/repo/version/VersionMigrator.java b/source/java/org/alfresco/repo/version/VersionMigrator.java index 390c989f86..7043c0e71b 100644 --- a/source/java/org/alfresco/repo/version/VersionMigrator.java +++ b/source/java/org/alfresco/repo/version/VersionMigrator.java @@ -28,10 +28,11 @@ import java.util.Map; import java.util.Set; import org.alfresco.model.ContentModel; +import org.alfresco.repo.admin.patch.impl.MigrateVersionStorePatch; import org.alfresco.repo.batch.BatchProcessor; import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorkerAdaptor; import org.alfresco.repo.domain.hibernate.SessionSizeResourceManager; -import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.repo.lock.JobLockService; import org.alfresco.repo.node.MLPropertyInterceptor; import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.policy.PolicyScope; @@ -72,6 +73,7 @@ public class VersionMigrator implements ApplicationEventPublisherAware private static final String MSG_PATCH_NOOP = "version_service.migration.patch.noop"; private static final String MSG_PATCH_COMPLETE = "version_service.migration.patch.complete"; + private static final String MSG_PATCH_IN_PROGRESS = "version_service.migration.patch.in_progress"; private static final String MSG_PATCH_SKIP1 = "version_service.migration.patch.warn.skip1"; private static final String MSG_PATCH_SKIP2 = "version_service.migration.patch.warn.skip2"; @@ -91,12 +93,16 @@ public class VersionMigrator implements ApplicationEventPublisherAware private DictionaryService dictionaryService; private TransactionService transactionService; private NodeService versionNodeService; // NodeService impl which redirects to appropriate VersionService - private QNameDAO qnameDAO; private RuleService ruleService; + private JobLockService jobLockService; private ApplicationEventPublisher applicationEventPublisher; + private Boolean migrationComplete = null; + private int loggingInterval = 500; + //private String lockToken = null; + public void setVersion2ServiceImpl(Version2ServiceImpl versionService) { this.version2Service = versionService; @@ -127,16 +133,16 @@ public class VersionMigrator implements ApplicationEventPublisherAware this.versionNodeService = versionNodeService; } - public void setQnameDAO(QNameDAO qnameDAO) - { - this.qnameDAO = qnameDAO; - } - public void setRuleService(RuleService ruleService) { this.ruleService = ruleService; } + public void setJobLockService(JobLockService jobLockService) + { + this.jobLockService = jobLockService; + } + public void setLoggingInterval(int loggingInterval) { this.loggingInterval = loggingInterval; @@ -158,13 +164,6 @@ public class VersionMigrator implements ApplicationEventPublisherAware public NodeRef migrateVersionHistory(NodeRef oldVHNodeRef, NodeRef versionedNodeRef) { - /* - if (logger.isTraceEnabled()) - { - logger.trace("migrateVersionHistory: oldVersionHistoryRef = " + oldVHNodeRef); - } - */ - VersionHistory vh = v1BuildVersionHistory(oldVHNodeRef, versionedNodeRef); // create new version history node @@ -202,13 +201,6 @@ public class VersionMigrator implements ApplicationEventPublisherAware NodeRef versionedNodeRef = oldVersion.getVersionedNodeRef(); // nodeRef to versioned node in live store NodeRef frozenStateNodeRef = oldVersion.getFrozenStateNodeRef(); // nodeRef to version node in version store - /* - if (logger.isTraceEnabled()) - { - logger.trace("v2CreateNewVersion: oldVersionRef = " + frozenStateNodeRef + " " + oldVersion); - } - */ - String versionLabel = oldVersion.getVersionLabel(); String versionDescription = oldVersion.getDescription(); @@ -336,7 +328,7 @@ public class VersionMigrator implements ApplicationEventPublisherAware return dbNodeService.getChildAssocs(rootNodeRef); } - private void migrateVersion(NodeRef oldVHNodeRef, boolean deleteImmediately) throws Throwable + protected void migrateVersion(NodeRef oldVHNodeRef, boolean deleteImmediately) throws Throwable { NodeRef versionedNodeRef = v1GetVersionedNodeRef(oldVHNodeRef); migrateVersionHistory(oldVHNodeRef, versionedNodeRef); @@ -353,44 +345,48 @@ public class VersionMigrator implements ApplicationEventPublisherAware } } + public boolean isMigrationComplete() + { + if (migrationComplete == null) + { + NodeRef oldRootNodeRef = dbNodeService.getRootNode(VersionMigrator.VERSION_STORE_REF_OLD); + final List childAssocRefs = getVersionHistories(oldRootNodeRef); + + migrationComplete = (childAssocRefs.size() == 0); + } + + return migrationComplete; + } + + /** + * Attempts to refresh the lock. + * + * @return Returns the lock token + */ + private void refreshLock(String lockToken) + { + if ((lockToken == null) || (jobLockService == null)) + { + return; + } + jobLockService.refreshLock(lockToken, MigrateVersionStorePatch.LOCK, MigrateVersionStorePatch.LOCK_TTL); + + if (logger.isTraceEnabled()) + { + logger.trace("Refreshed lock: "+lockToken+" with TTL of "+MigrateVersionStorePatch.LOCK_TTL+" ms ["+AlfrescoTransactionSupport.getTransactionId()+"]["+Thread.currentThread().getId()+"]"); + } + } + /** * Do the Version migration work - * @return Returns true if the work is done + * @return Returns null if no work to do, true if the work is done, false is incomplete (or in progress) */ - public boolean migrateVersions(int batchSize, int threadCount, final boolean deleteImmediately) + public Boolean migrateVersions(int batchSize, int threadCount, int limit, final boolean deleteImmediately, final String lockToken, boolean isRunningAsJob) { - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() - { - public Object execute() throws Throwable - { - // pre-create new qnames (rather than retry initial batches) - qnameDAO.getOrCreateQName(Version2Model.ASPECT_VERSION_STORE_ROOT); - qnameDAO.getOrCreateQName(Version2Model.TYPE_QNAME_VERSION_HISTORY); - qnameDAO.getOrCreateQName(Version2Model.PROP_QNAME_VERSIONED_NODE_ID); - qnameDAO.getOrCreateQName(Version2Model.ASPECT_VERSION); - qnameDAO.getOrCreateQName(Version2Model.PROP_QNAME_VERSION_LABEL); - qnameDAO.getOrCreateQName(Version2Model.PROP_QNAME_VERSION_NUMBER); - qnameDAO.getOrCreateQName(Version2Model.PROP_QNAME_FROZEN_NODE_REF); - qnameDAO.getOrCreateQName(Version2Model.PROP_QNAME_FROZEN_NODE_DBID); - qnameDAO.getOrCreateQName(Version2Model.PROP_QNAME_FROZEN_CREATOR); - qnameDAO.getOrCreateQName(Version2Model.PROP_QNAME_FROZEN_CREATED); - qnameDAO.getOrCreateQName(Version2Model.PROP_QNAME_FROZEN_MODIFIER); - qnameDAO.getOrCreateQName(Version2Model.PROP_QNAME_FROZEN_MODIFIED); - qnameDAO.getOrCreateQName(Version2Model.PROP_QNAME_FROZEN_ACCESSED); - qnameDAO.getOrCreateQName(Version2Model.ASSOC_SUCCESSOR); - qnameDAO.getOrCreateQName(Version2Model.CHILD_QNAME_VERSION_HISTORIES); - qnameDAO.getOrCreateQName(Version2Model.CHILD_QNAME_VERSIONS); - qnameDAO.getOrCreateQName(Version2Model.CHILD_QNAME_VERSIONED_ASSOCS); - qnameDAO.getOrCreateQName(Version2Model.PROP_QNAME_ASSOC_DBID); - qnameDAO.getOrCreateQName(Version2Model.PROP_QNAME_TRANSLATION_VERSIONS); - - return null; - } - }, false, true); - final NodeRef oldRootNodeRef = dbNodeService.getRootNode(VersionMigrator.VERSION_STORE_REF_OLD); final NodeRef newRootNodeRef = dbNodeService.getRootNode(VersionMigrator.VERSION_STORE_REF_NEW); + refreshLock(lockToken); long startTime = System.currentTimeMillis(); @@ -400,10 +396,16 @@ public class VersionMigrator implements ApplicationEventPublisherAware if (toDo == 0) { - logger.info(I18NUtil.getMessage(MSG_PATCH_NOOP)); - return true; + if (logger.isDebugEnabled()) + { + logger.debug(I18NUtil.getMessage(MSG_PATCH_NOOP)); + } + migrationComplete = true; + return null; } + migrationComplete = false; + if (logger.isInfoEnabled()) { logger.info("Found "+childAssocRefs.size()+" version histories in old version store (in "+((System.currentTimeMillis()-startTime)/1000)+" secs)"); @@ -416,14 +418,19 @@ public class VersionMigrator implements ApplicationEventPublisherAware long splitTime = System.currentTimeMillis(); - final List newChildAssocRefs = getVersionHistories(newRootNodeRef); - final boolean firstMigration = (newChildAssocRefs.size() == 0); + boolean firstMigration = false; - if (logger.isInfoEnabled()) + if (! isRunningAsJob) { - if (! firstMigration) + List newChildAssocRefs = getVersionHistories(newRootNodeRef); + firstMigration = (newChildAssocRefs.size() == 0); + + if (logger.isInfoEnabled()) { - logger.warn("This is not the first migration attempt. Found "+newChildAssocRefs.size()+" version histories in new version store (in "+((System.currentTimeMillis()-splitTime)/1000)+" secs)"); + if (! firstMigration) + { + logger.warn("This is not the first migration attempt. Found "+newChildAssocRefs.size()+" version histories in new version store (in "+((System.currentTimeMillis()-splitTime)/1000)+" secs)"); + } } } @@ -444,15 +451,27 @@ public class VersionMigrator implements ApplicationEventPublisherAware splitTime = System.currentTimeMillis(); + int toMigrateCount = 0; int totalCount = 0; - Collection> batchProcessorWork = new ArrayList>(2); + + List> batchProcessorWork = new ArrayList>(2); final List tmpBatch = new ArrayList(batchSize); + int maxToDo = childAssocRefs.size(); + + if (limit > -1) + { + maxToDo = limit; + + if (logger.isInfoEnabled()) + { + logger.info("Limit this job cycle to max of "+limit+" version histories"); + } + } + for (final ChildAssociationRef childAssocRef : childAssocRefs) { - totalCount++; - // short-cut if first migration if (!firstMigration) { @@ -464,16 +483,28 @@ public class VersionMigrator implements ApplicationEventPublisherAware } } + toMigrateCount++; + if (tmpBatch.size() < batchSize) { tmpBatch.add(childAssocRef.getChildRef()); } - if ((tmpBatch.size() == batchSize) || (totalCount == childAssocRefs.size())) + if ((tmpBatch.size() == batchSize) || + (toMigrateCount >= maxToDo) || + (totalCount == childAssocRefs.size())) { - // Each thread gets 1 batch to execute - batchProcessorWork.add(new ArrayList(tmpBatch)); - tmpBatch.clear(); + if (tmpBatch.size() > 0) + { + // Each thread gets 1 batch to execute + batchProcessorWork.add(new ArrayList(tmpBatch)); + tmpBatch.clear(); + } + } + + if (toMigrateCount >= maxToDo) + { + break; } } @@ -483,7 +514,7 @@ public class VersionMigrator implements ApplicationEventPublisherAware { if (logger.isDebugEnabled()) { - logger.debug("Split into "+batchCount+" batches in "+(System.currentTimeMillis()-splitTime)+" ms"); + logger.debug("Split "+toMigrateCount+" into "+batchCount+" batches in "+(System.currentTimeMillis()-splitTime)+" ms"); } // @@ -502,7 +533,7 @@ public class VersionMigrator implements ApplicationEventPublisherAware // Authentication AuthenticationUtil.setRunAsUser(runAsUser); } - + public void afterProcess() throws Throwable { // Enable rules @@ -515,6 +546,8 @@ public class VersionMigrator implements ApplicationEventPublisherAware { long startTime = System.currentTimeMillis(); + refreshLock(lockToken); + for (NodeRef oldVHNodeRef : vhBatch) { migrateVersion(oldVHNodeRef, deleteImmediately); @@ -527,16 +560,66 @@ public class VersionMigrator implements ApplicationEventPublisherAware } }; - BatchProcessor> batchProcessor = new BatchProcessor>( - "MigrateVersionStore", - transactionService.getRetryingTransactionHelper(), - batchProcessorWork, threadCount, 1, - applicationEventPublisher, logger, loggingInterval); - boolean splitTxns = true; - batchProcessor.process(batchProcessorWorker, splitTxns); - batchErrorCount = batchProcessor.getTotalErrors(); + if (threadCount > 1) + { + // process first batch only - to avoid retries on qname, acl + + List> batchProcessorWorkFirst = new ArrayList>(2); + batchProcessorWorkFirst.add(new ArrayList(batchProcessorWork.get(0))); + + BatchProcessor> batchProcessorFirst = new BatchProcessor>( + "MigrateVersionStore", + transactionService.getRetryingTransactionHelper(), + batchProcessorWorkFirst, threadCount, 1, + applicationEventPublisher, logger, loggingInterval); + + batchProcessorFirst.process(batchProcessorWorker, splitTxns); + + batchErrorCount = batchProcessorFirst.getTotalErrors(); + + batchProcessorWork.remove(0); + } + + if (batchProcessorWork.size() > 0) + { + // process remaining batches + BatchProcessor> batchProcessor = new BatchProcessor>( + "MigrateVersionStore", + transactionService.getRetryingTransactionHelper(), + batchProcessorWork, threadCount, 1, + applicationEventPublisher, logger, loggingInterval); + + batchProcessor.process(batchProcessorWorker, splitTxns); + + batchErrorCount = batchErrorCount + batchProcessor.getTotalErrors(); + } + } + + if (alreadyMigratedCount > 0) + { + logger.warn(I18NUtil.getMessage(MSG_PATCH_SKIP2, alreadyMigratedCount)); + } + + if (batchCount > 0) + { + if (batchErrorCount > 0) + { + logger.warn(I18NUtil.getMessage(MSG_PATCH_SKIP1, batchErrorCount, batchCount, ((System.currentTimeMillis()-startTime)/1000))); + } + else + { + if ((limit == -1) || ((toMigrateCount+alreadyMigratedCount) == toDo)) + { + logger.info(I18NUtil.getMessage(MSG_PATCH_COMPLETE, toMigrateCount, toDo, ((System.currentTimeMillis()-startTime)/1000), deleteImmediately)); + migrationComplete = true; + } + else + { + logger.info(I18NUtil.getMessage(MSG_PATCH_IN_PROGRESS, toMigrateCount, toDo, ((System.currentTimeMillis()-startTime)/1000), deleteImmediately)); + } + } } } finally @@ -545,25 +628,7 @@ public class VersionMigrator implements ApplicationEventPublisherAware SessionSizeResourceManager.setEnableInTransaction(); } - - if (alreadyMigratedCount > 0) - { - logger.warn(I18NUtil.getMessage(MSG_PATCH_SKIP2, alreadyMigratedCount)); - } - - if (batchCount > 0) - { - if (batchErrorCount > 0) - { - logger.warn(I18NUtil.getMessage(MSG_PATCH_SKIP1, batchErrorCount, batchCount, ((System.currentTimeMillis()-startTime)/1000))); - } - else - { - logger.info(I18NUtil.getMessage(MSG_PATCH_COMPLETE, (toDo - alreadyMigratedCount), toDo, ((System.currentTimeMillis()-startTime)/1000))); - } - } - - return (batchErrorCount == 0); + return migrationComplete; } @@ -583,12 +648,21 @@ public class VersionMigrator implements ApplicationEventPublisherAware { long startTime = System.currentTimeMillis(); + if (logger.isDebugEnabled()) + { + logger.debug("batchSize="+batchSize+", batchWorkerThreadCount="+threadCount); + } + NodeRef oldRootNodeRef = dbNodeService.getRootNode(VersionMigrator.VERSION_STORE_REF_OLD); List childAssocRefs = getVersionHistories(oldRootNodeRef); int toDo = childAssocRefs.size(); - if (toDo > 0) + if (toDo == 0) + { + migrationComplete = true; + } + else { if (logger.isInfoEnabled()) { @@ -645,9 +719,9 @@ public class VersionMigrator implements ApplicationEventPublisherAware if (batchCount > 0) { - if (logger.isInfoEnabled()) + if (logger.isDebugEnabled()) { - logger.info("Split into "+batchCount+" batches in "+(System.currentTimeMillis()-splitTime)+" ms"); + logger.debug("Split into "+batchCount+" batches in "+(System.currentTimeMillis()-splitTime)+" ms"); } // @@ -674,7 +748,7 @@ public class VersionMigrator implements ApplicationEventPublisherAware }; BatchProcessor> batchProcessor = new BatchProcessor>( - "MigrateVersionStore", + "CleanOldVersionStore", transactionService.getRetryingTransactionHelper(), batchProcessorWork, threadCount, 1, applicationEventPublisher, logger, loggingInterval); @@ -705,6 +779,12 @@ public class VersionMigrator implements ApplicationEventPublisherAware else { logger.info(I18NUtil.getMessage(MSG_DELETE_COMPLETE, (toDo - notMigratedCount), toDo, ((System.currentTimeMillis()-startTime)/1000))); + + if (notMigratedCount == 0) + { + migrationComplete = null; + isMigrationComplete(); + } } } } diff --git a/source/java/org/alfresco/repo/version/VersionMigratorTest.java b/source/java/org/alfresco/repo/version/VersionMigratorTest.java index 0332d0bcc2..9cd332872b 100644 --- a/source/java/org/alfresco/repo/version/VersionMigratorTest.java +++ b/source/java/org/alfresco/repo/version/VersionMigratorTest.java @@ -358,7 +358,7 @@ public class VersionMigratorTest extends BaseVersionStoreTest public NodeRef execute() throws Throwable { // Migrate (and don't delete old version history) ! - versionMigrator.migrateVersions(1, 1, false); + versionMigrator.migrateVersions(1, 1, -1, false, null, false); return null; } diff --git a/source/java/org/alfresco/repo/version/common/VersionHistoryImpl.java b/source/java/org/alfresco/repo/version/common/VersionHistoryImpl.java index 65feeabc32..b78e708c42 100644 --- a/source/java/org/alfresco/repo/version/common/VersionHistoryImpl.java +++ b/source/java/org/alfresco/repo/version/common/VersionHistoryImpl.java @@ -125,7 +125,7 @@ public class VersionHistoryImpl implements VersionHistory * Gets a collection containing all the versions within the * version history. *

- * Versions are returned in descending create date order. + * Versions are returned in descending create date order (most recent first). * * @return collection containing all the versions */ diff --git a/source/java/org/alfresco/repo/version/common/VersionLabelComparator.java b/source/java/org/alfresco/repo/version/common/VersionLabelComparator.java index 84a4be54a2..03368956b1 100644 --- a/source/java/org/alfresco/repo/version/common/VersionLabelComparator.java +++ b/source/java/org/alfresco/repo/version/common/VersionLabelComparator.java @@ -21,21 +21,24 @@ package org.alfresco.repo.version.common; import java.util.Comparator; import org.alfresco.service.cmr.version.Version; +import org.alfresco.util.VersionNumber; /** - * A comparator to sort a version list according theires version labels ascending + * A comparator to sort a version list according to their version labels in descending order (eg. 2.1, 2.0, 1.1, 1.0) * * @author Yanick Pignot + * + * @deprecated see VersionHistory (getAllVersions, VersionComparatorAsc, VersionComparatorDesc) */ -public class VersionLabelComparator implements Comparator +public class VersionLabelComparator implements Comparator { - public int compare(Object version1, Object version2) + public int compare(Version version1, Version version2) { - String labelV1 = ((Version) version1).getVersionLabel(); - String labelV2 = ((Version) version2).getVersionLabel(); + String labelV1 = version1.getVersionLabel(); + String labelV2 = version2.getVersionLabel(); - // sort the list ascending - return labelV2.compareTo(labelV1); + // sort the list descending (ie. most recent first) + return new VersionNumber(labelV2).compareTo(new VersionNumber(labelV1)); } } diff --git a/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoJavaScript.java b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoJavaScript.java index 1f302163da..b4a717bc6d 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoJavaScript.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoJavaScript.java @@ -27,6 +27,7 @@ import java.util.Map; import org.alfresco.model.ContentModel; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.ScriptService; @@ -34,9 +35,12 @@ import org.alfresco.service.cmr.workflow.WorkflowException; import org.dom4j.Element; import org.jbpm.context.def.VariableAccess; import org.jbpm.context.exe.ContextInstance; +import org.jbpm.graph.def.Node; import org.jbpm.graph.exe.ExecutionContext; import org.jbpm.graph.exe.Token; import org.jbpm.jpdl.xml.JpdlXmlReader; +import org.jbpm.taskmgmt.def.Task; +import org.jbpm.taskmgmt.exe.TaskInstance; import org.springframework.beans.factory.BeanFactory; import org.xml.sax.InputSource; @@ -64,7 +68,6 @@ public class AlfrescoJavaScript extends JBPMSpringActionHandler private Element script; private String runas; - /* (non-Javadoc) * @see org.alfresco.repo.workflow.jbpm.JBPMSpringActionHandler#initialiseHandler(org.springframework.beans.factory.BeanFactory) */ @@ -74,11 +77,9 @@ public class AlfrescoJavaScript extends JBPMSpringActionHandler services = (ServiceRegistry)factory.getBean(ServiceRegistry.SERVICE_REGISTRY); } - /* (non-Javadoc) * @see org.jbpm.graph.def.ActionHandler#execute(org.jbpm.graph.exe.ExecutionContext) */ - @SuppressWarnings("unchecked") public void execute(final ExecutionContext executionContext) throws Exception { // validate script @@ -86,64 +87,13 @@ public class AlfrescoJavaScript extends JBPMSpringActionHandler { throw new WorkflowException("Script has not been provided"); } + boolean isTextOnly = isScriptOnlyText(); - // extract action configuration - String expression = null; - List variableAccesses = null; - - // is the script specified as text only, or as explicit expression, variable elements - boolean isTextOnly = true; - Iterator iter = script.elementIterator(); - while (iter.hasNext()) - { - Element element = iter.next(); - if (element.getNodeType() == org.dom4j.Node.ELEMENT_NODE) - { - isTextOnly = false; - } - } - - // extract script and variables - if (isTextOnly) - { - expression = script.getTextTrim(); - } - else - { - variableAccesses = jpdlReader.readVariableAccesses(script); - Element expressionElement = script.element("expression"); - if (expressionElement == null) - { - throw new WorkflowException("Script expression has not been provided"); - } - expression = expressionElement.getTextTrim(); - } + List variableAccesses = getVariableAccessors(isTextOnly); + String expression = getExpression(isTextOnly); // execute - Object result = null; - if (runas == null) - { - result = executeScript(executionContext, services, expression, variableAccesses); - } - else - { - boolean runasExists = services.getPersonService().personExists(runas); - if (!runasExists) - { - throw new WorkflowException("runas user '" + runas + "' does not exist."); - } - - // execute as specified runas user - final String expr = expression; - final List varAcc = variableAccesses; - result = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { - public Object doWork() throws Exception - { - return executeScript(executionContext, services, expr, varAcc); - } - }, runas); - } + Object result = executeExpression(expression, executionContext, variableAccesses); // map script return variable to process context VariableAccess returnVariable = getWritableVariable(variableAccesses); @@ -151,10 +101,149 @@ public class AlfrescoJavaScript extends JBPMSpringActionHandler { ContextInstance contextInstance = executionContext.getContextInstance(); Token token = executionContext.getToken(); - contextInstance.setVariable(returnVariable.getVariableName(), result, token); + contextInstance.setVariable(returnVariable.getVariableName(), result, token); } } + private Object executeExpression(String expression, ExecutionContext executionContext, List variableAccesses) { + boolean userChanged = checkFullyAuthenticatedUser(executionContext); + Object result = executeScript(expression, executionContext, variableAccesses); + if(userChanged) + { + AuthenticationUtil.clearCurrentSecurityContext(); + } + return result; + } + + private Object executeScript(String expression, + ExecutionContext executionContext, + List variableAccesses) + { + String user = AuthenticationUtil.getFullyAuthenticatedUser(); + if (runas == null && user !=null) + { + return executeScript(executionContext, services, expression, variableAccesses); + } + else + { + String runAsUser = runas; + if(runAsUser == null) + { + runAsUser = AuthenticationUtil.getSystemUserName(); + } else + { + validateRunAsUser(); + } + return executeScriptAs(runAsUser, expression, executionContext, services, variableAccesses); + } + } + + private static Object executeScriptAs(String runAsUser, + final String expression, + final ExecutionContext executionContext, + final ServiceRegistry services, + final List variableAccesses) { + // execute as specified runAsUser + return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public Object doWork() throws Exception + { + return executeScript(executionContext, services, expression, variableAccesses); + } + }, runAsUser); + } + + /** + * Checks a valid Fully Authenticated User is set. + * If none is set then attempts to set the task assignee as the Fully Authenticated User. + * @param executionContext + * @return true if the Fully Authenticated User was changes, otherwise false. + */ + private boolean checkFullyAuthenticatedUser(final ExecutionContext executionContext) { + if(AuthenticationUtil.getFullyAuthenticatedUser()!= null) + return false; + TaskInstance taskInstance = executionContext.getTaskInstance(); + if(taskInstance!=null) + { + String userName = taskInstance.getActorId(); + if (userName != null) + { + AuthenticationUtil.setFullyAuthenticatedUser(userName); + return true; + } + } + return false; + } + + /** + * Checks that the specified 'runas' field + * specifies a valid username. + */ + private void validateRunAsUser() { + Boolean runAsExists = AuthenticationUtil.runAs(new RunAsWork() + { // Validate using System user to ensure sufficient permissions available to access person node. + + public Boolean doWork() throws Exception { + return services.getPersonService().personExists(runas); + } + }, AuthenticationUtil.getSystemUserName()); + if (!runAsExists) + { + throw new WorkflowException("runas user '" + runas + "' does not exist."); + } + } + + /** + * Gets the expression {@link String} from the script. + * @param isTextOnly Is the script text only or is it XML? + * @return the expression {@link String}. + */ + private String getExpression(boolean isTextOnly) { + if (isTextOnly) + { + return script.getTextTrim(); + } + else + { + Element expressionElement = script.element("expression"); + if (expressionElement == null) + { + throw new WorkflowException("Script expression has not been provided"); + } + return expressionElement.getTextTrim(); + } + } + + @SuppressWarnings("unchecked") + private List getVariableAccessors(boolean isTextOnly) { + if(isTextOnly) + { + return null; + } + else + { + return jpdlReader.readVariableAccesses(script); + } + } + + /** + * Is the script specified as text only, or as explicit expression, variable elements + * @return + */ + @SuppressWarnings("unchecked") + private boolean isScriptOnlyText() { + Iterator iter = script.elementIterator(); + while (iter.hasNext()) + { + Element element = iter.next(); + if (element.getNodeType() == org.dom4j.Node.ELEMENT_NODE) + { + return false; + } + } + return true; + } + /** * Execute a script @@ -244,18 +333,16 @@ public class AlfrescoJavaScript extends JBPMSpringActionHandler * @param variableAccesses the variable configuration * @return the map of script arguments */ - @SuppressWarnings("unchecked") private static Map createInputMap(ExecutionContext executionContext, ServiceRegistry services, List variableAccesses) { Map inputMap = new HashMap(); // initialise global script variables - String userName = AuthenticationUtil.getFullyAuthenticatedUser(); - NodeRef person = services.getPersonService().getPerson(userName); - if (person != null) + JBPMNode personNode = getPersonNode(executionContext, services); + if (personNode != null) { - inputMap.put("person", new JBPMNode(person, services)); - NodeRef homeSpace = (NodeRef)services.getNodeService().getProperty(person, ContentModel.PROP_HOMEFOLDER); + inputMap.put("person", personNode ); + NodeRef homeSpace = (NodeRef)services.getNodeService().getProperty(personNode.getNodeRef(), ContentModel.PROP_HOMEFOLDER); if (homeSpace != null) { inputMap.put("userhome", new JBPMNode(homeSpace, services)); @@ -266,17 +353,20 @@ public class AlfrescoJavaScript extends JBPMSpringActionHandler Token token = executionContext.getToken(); inputMap.put("executionContext", executionContext); inputMap.put("token", token); - if (executionContext.getNode() != null) + Node node = executionContext.getNode(); + if (node != null) { - inputMap.put("node", executionContext.getNode()); + inputMap.put("node", node); } - if (executionContext.getTask() != null) + Task task = executionContext.getTask(); + if (task != null) { - inputMap.put("task", executionContext.getTask()); + inputMap.put("task", task); } - if (executionContext.getTaskInstance() != null) + TaskInstance taskInstance = executionContext.getTaskInstance(); + if (taskInstance != null) { - inputMap.put("taskInstance", executionContext.getTaskInstance()); + inputMap.put("taskInstance", taskInstance); } // if no readable variableInstances are specified, @@ -284,10 +374,10 @@ public class AlfrescoJavaScript extends JBPMSpringActionHandler if (!hasReadableVariable(variableAccesses)) { // copy all the variableInstances of the context into the interpreter - Map variables = contextInstance.getVariables(token); + Map variables = contextInstance.getVariables(token); if (variables != null) { - for (Map.Entry entry : variables.entrySet()) + for (Map.Entry entry : variables.entrySet()) { String variableName = (String) entry.getKey(); Object variableValue = entry.getValue(); @@ -312,6 +402,20 @@ public class AlfrescoJavaScript extends JBPMSpringActionHandler return inputMap; } + + + private static JBPMNode getPersonNode(ExecutionContext executionContext, ServiceRegistry services) { + String userName = AuthenticationUtil.getFullyAuthenticatedUser(); + if(userName != null) + { + NodeRef person = services.getPersonService().getPerson(userName); + if(person !=null) + { + return new JBPMNode(person, services); + } + } + return null; + } /** @@ -362,4 +466,12 @@ public class AlfrescoJavaScript extends JBPMSpringActionHandler return writable; } + public void setScript(Element script) { + this.script = script; + } + + public void setRunas(String runas) { + this.runas = runas; + } + } diff --git a/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoJavaScriptIntegrationTest.java b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoJavaScriptIntegrationTest.java new file mode 100644 index 0000000000..81b3206536 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoJavaScriptIntegrationTest.java @@ -0,0 +1,184 @@ +package org.alfresco.repo.workflow.jbpm; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.HashMap; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.scripts.ScriptException; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.BaseAlfrescoSpringTest; +import org.alfresco.util.GUID; +import org.alfresco.util.PropertyMap; +import org.dom4j.DocumentHelper; +import org.dom4j.Element; +import org.jbpm.context.exe.ContextInstance; +import org.jbpm.graph.exe.ExecutionContext; +import org.jbpm.graph.exe.Token; +import org.jbpm.taskmgmt.exe.TaskInstance; + +public class AlfrescoJavaScriptIntegrationTest extends BaseAlfrescoSpringTest +{ + private static final QName fooName = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "Foo"); + private static final QName barName = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "Bar"); + private static final QName docName = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "Doc"); + private static final String BASIC_USER = "basic"+GUID.generate(); + private static String systemUser = AuthenticationUtil.getSystemUserName(); + + private ServiceRegistry services; + private ExecutionContext context; + private HashMap variables; + private PersonService personService; + + /** + * Test that JavaScript can still be run even if no Authentication is provided. + * This can occur if, e.g. the action is executed as part of an asynchronous task. + * @throws Exception + */ + public void testRunsWithoutAuthentication() throws Exception { + NodeRef systemNode = personService.getPerson(systemUser); + NodeRef baseUserNode = personService.getPerson(BASIC_USER); + TestUserStore userStore = new TestUserStore(); + variables.put("userStore", userStore); + Element script = buildScript("userStore.storeUsers(person)"); + + // Check authentication cleared. + AuthenticationUtil.clearCurrentSecurityContext(); + assertNull(AuthenticationUtil.getFullyAuthenticatedUser()); + assertNull(AuthenticationUtil.getRunAsUser()); + + // Check uses system user when no authentication set and no task assignee. + AlfrescoJavaScript scriptHandler = new AlfrescoJavaScript(); + scriptHandler.setScript(script); + scriptHandler.execute(context); + assertEquals(systemUser, userStore.runAsUser); + assertEquals(systemUser, userStore.fullUser); + assertEquals(systemNode, userStore.person.getNodeRef()); + + // Check authentication is correctly reset. + assertNull(AuthenticationUtil.getFullyAuthenticatedUser()); + assertNull(AuthenticationUtil.getRunAsUser()); + + // Check that when a task assignee exists, then he/she is used for authentication. + TaskInstance taskInstance = mock(TaskInstance.class); + when(taskInstance.getActorId()).thenReturn(BASIC_USER); + when(context.getTaskInstance()).thenReturn(taskInstance); + scriptHandler = new AlfrescoJavaScript(); + scriptHandler.setScript(script); + scriptHandler.execute(context); + assertEquals(BASIC_USER, userStore.runAsUser); + assertEquals(BASIC_USER, userStore.fullUser); + assertEquals(baseUserNode, userStore.person.getNodeRef()); + + // Check authentication is correctly reset. + assertNull(AuthenticationUtil.getFullyAuthenticatedUser()); + assertNull(AuthenticationUtil.getRunAsUser()); + } + + /** + * See Jira issue ALF-657. + * @throws Exception + */ + public void testRunAsAdminMoveContent() throws Exception { + NodeRef fooFolder = nodeService.createNode(rootNodeRef, + ContentModel.ASSOC_CONTAINS, + fooName, + ContentModel.TYPE_FOLDER).getChildRef(); + + NodeRef barFolder = nodeService.createNode(rootNodeRef, + ContentModel.ASSOC_CONTAINS, + barName, + ContentModel.TYPE_FOLDER).getChildRef(); + + NodeRef doc = nodeService.createNode(fooFolder, + ContentModel.ASSOC_CONTAINS, + docName, + ContentModel.TYPE_CONTENT).getChildRef(); + PermissionService permissions = services.getPermissionService(); + permissions.setPermission(doc, BASIC_USER, PermissionService.ALL_PERMISSIONS, true); + AuthenticationUtil.setFullyAuthenticatedUser(BASIC_USER); + + Element script = buildScript("doc.move(bar)"); + + variables.put("doc", new JBPMNode(doc, services)); + variables.put("bar", new JBPMNode(barFolder, services)); + assertEquals(fooFolder, nodeService.getPrimaryParent(doc).getParentRef()); + try + { + AlfrescoJavaScript scriptHandler = new AlfrescoJavaScript(); + scriptHandler.setScript(script); + scriptHandler.execute(context); + fail("The user should not have permission to write to bar!"); + } catch(ScriptException e) + { + // Do nothing. + } + + assertEquals(fooFolder, nodeService.getPrimaryParent(doc).getParentRef()); + AlfrescoJavaScript scriptHandler = new AlfrescoJavaScript(); + scriptHandler.setScript(script); + scriptHandler.setRunas(AuthenticationUtil.getAdminUserName()); + scriptHandler.execute(context); + assertEquals(barFolder, nodeService.getPrimaryParent(doc).getParentRef()); + } + + private Element buildScript(String expression) { + Element script = DocumentHelper.createElement("script"); + script.setText(expression); + return script; + } + + @Override + @SuppressWarnings("deprecation") + protected void onSetUp() throws Exception { + super.onSetUp(); + services = (ServiceRegistry) applicationContext.getBean("ServiceRegistry"); + personService = services.getPersonService(); + createUser(BASIC_USER); + + // Sets up the Execution Context + context = mock(ExecutionContext.class); + ContextInstance contextInstance = mock(ContextInstance.class); + when(context.getContextInstance()).thenReturn(contextInstance); + variables = new HashMap(); + when(contextInstance.getVariables()).thenReturn(variables); + when(contextInstance.getVariables((Token) any())).thenReturn(variables); + } + + private void createUser(String userName) + { + if (this.authenticationService.authenticationExists(userName) == false) + { + this.authenticationService.createAuthentication(userName, "PWD".toCharArray()); + + PropertyMap ppOne = new PropertyMap(4); + ppOne.put(ContentModel.PROP_USERNAME, userName); + ppOne.put(ContentModel.PROP_FIRSTNAME, "firstName"); + ppOne.put(ContentModel.PROP_LASTNAME, "lastName"); + ppOne.put(ContentModel.PROP_EMAIL, "email@email.com"); + ppOne.put(ContentModel.PROP_JOBTITLE, "jobTitle"); + personService.createPerson(ppOne); + } + } + + public static class TestUserStore { + private String runAsUser; + private String fullUser; + private JBPMNode person = null; + + public void storeUsers(JBPMNode person) + { + fullUser = AuthenticationUtil.getFullyAuthenticatedUser(); + runAsUser = AuthenticationUtil.getRunAsUser(); + this.person = person; + } + } +} diff --git a/source/java/org/alfresco/service/cmr/dictionary/ModelDefinition.java b/source/java/org/alfresco/service/cmr/dictionary/ModelDefinition.java index e7d33d5408..bbdaee0b2e 100644 --- a/source/java/org/alfresco/service/cmr/dictionary/ModelDefinition.java +++ b/source/java/org/alfresco/service/cmr/dictionary/ModelDefinition.java @@ -18,6 +18,7 @@ */ package org.alfresco.service.cmr.dictionary; +import java.util.Collection; import java.util.Date; import org.alfresco.service.namespace.QName; @@ -54,5 +55,26 @@ public interface ModelDefinition * @return the model version */ public String getVersion(); + + /** + * @return the namespaces defined by this model + */ + public Collection getNamespaces(); + /** + * @param uri namespace uri + * @return true => model defines the uri + */ + public boolean isNamespaceDefined(String uri); + + /** + * @return the namespaces imported by this model + */ + public Collection getImportedNamespaces(); + + /** + * @param uri namespace uri + * @return true => model imports the uri + */ + public boolean isNamespaceImported(String uri); } diff --git a/source/java/org/alfresco/service/cmr/version/VersionHistory.java b/source/java/org/alfresco/service/cmr/version/VersionHistory.java index 2621fb734c..9e3f60e2b4 100644 --- a/source/java/org/alfresco/service/cmr/version/VersionHistory.java +++ b/source/java/org/alfresco/service/cmr/version/VersionHistory.java @@ -50,7 +50,7 @@ public interface VersionHistory extends Serializable * Gets a collection containing all the versions within the * version history. *

- * The order of the versions is not guarenteed. + * Versions are returned in descending create date order (most recent first). * * @return collection containing all the versions */ diff --git a/source/java/org/alfresco/wcm/sandbox/script/Asset.java b/source/java/org/alfresco/wcm/sandbox/script/Asset.java index c3e9bb60b3..92ebac3e7d 100644 --- a/source/java/org/alfresco/wcm/sandbox/script/Asset.java +++ b/source/java/org/alfresco/wcm/sandbox/script/Asset.java @@ -18,21 +18,45 @@ */ package org.alfresco.wcm.sandbox.script; +import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Date; 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.avm.AVMNodeConverter; +import org.alfresco.repo.dictionary.DictionaryNamespaceComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.avm.AVMBadArgumentException; +import org.alfresco.service.cmr.avm.AVMNotFoundException; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.NamespaceException; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; -import org.springframework.extensions.surf.util.ISO8601DateFormat; import org.alfresco.wcm.asset.AssetInfo; import org.alfresco.wcm.asset.AssetService; import org.alfresco.wcm.sandbox.SandboxService; +import org.json.JSONException; +import org.json.JSONObject; +import org.springframework.extensions.surf.util.Content; +import org.springframework.extensions.surf.util.ISO8601DateFormat; /** * WCM Asset in a sandbox exposed over Java Script API. @@ -41,20 +65,22 @@ import org.alfresco.wcm.sandbox.SandboxService; */ public class Asset implements Serializable { - /** + private static final QName NAMESPACE_SERVICE = QName.createQName("", "namespaceService"); + /** * */ private static final long serialVersionUID = -5759260478423750966L; private AssetInfo asset; private Sandbox sandbox; private Map props; - + private Set updatedProperties = new HashSet(); + public Asset(Sandbox sandbox, AssetInfo asset) { this.sandbox = sandbox; this.asset = asset; } - + /** * The creator of this asset * @return the creator @@ -194,7 +220,7 @@ public class Asset implements Serializable Serializable propValue = intprops.get(qname); try { - propsX.put(prefixQname.toPrefixString(), propValue.toString()); + propsX.put(prefixQname.toPrefixString(), (null == propValue) ? (null):(propValue.toString())); } catch (NamespaceException ne) { // No local name, only thing I can do is use the full namke @@ -206,31 +232,243 @@ public class Asset implements Serializable return props; } - -// /** -// * Save the properties please note some system properties are protected and cannot be updated. If you attempt to -// * update a protected property your request will be ignored. -// * @param properties -// */ -// public void save() -// { -// if(props != null) -// { -// /** -// * Need to map the to a -// */ -// NamespaceService ns = getNamespaceService(); -// Map newProps = new HashMap(props.size()); -// for (String key : props.keySet()) -// { -// String value = props.get(key); -// QName q = QName.resolveToQName(ns, key); -// newProps.put(q, value); -// } -// getAssetService().setAssetProperties(asset, newProps); -// } -// } - + + /** + * Save the properties please note some system properties are protected and cannot be updated. If you attempt to update a protected property your request will be ignored. + * + * @param properties + */ + public void save() + { + if (!updatedProperties.isEmpty() && (null != props)) + { + Map newProps = new HashMap(props.size()); + QName type = getAssetType(); + DictionaryService dictionaryService = getDictionaryService(); + TypeDefinition typeDefinition = (null != type) ? (dictionaryService.getType(type)) : (null); + if (null != typeDefinition) + { + if (updatedProperties.contains(ContentModel.PROP_NAME)) + { + updatedProperties.remove(ContentModel.PROP_NAME); + rename(getPropertyValue(ContentModel.PROP_NAME)); + } + Map propertyDefinitions = typeDefinition.getProperties(); + for (QName key : updatedProperties) + { + PropertyDefinition propertyDefinition = (propertyDefinitions.containsKey(key)) ? (propertyDefinitions.get(key)) : (dictionaryService.getProperty(key)); + Serializable value = convertValueToDataType(key, propertyDefinition.getDataType().getName(), getPropertyValue(key)); + newProps.put(key, value); + } + getAssetService().setAssetProperties(asset, newProps); + updatedProperties.clear(); + } + else + { + throw new AVMNotFoundException("The type property of the current Asset not found"); + } + } + } + + private String getPropertyValue(QName key) + { + String prefixedQName = completeContentModelQName(key).toPrefixString(); + return props.containsKey(prefixedQName) ? (props.get(prefixedQName)) : (props.get(key.toString())); + } + + private Serializable convertValueToDataType(QName propertyName, QName dataType, String textualValue) + { + Serializable result = null; + if (null != textualValue) + { + try + { + if (DataTypeDefinition.BOOLEAN.equals(dataType)) + { + result = Boolean.parseBoolean(textualValue); + } + else if (DataTypeDefinition.DOUBLE.equals(dataType)) + { + result = Double.parseDouble(textualValue); + } + else if (DataTypeDefinition.FLOAT.equals(dataType)) + { + result = Float.parseFloat(textualValue); + } + else if (DataTypeDefinition.INT.equals(dataType)) + { + result = Integer.parseInt(textualValue); + } + else if (DataTypeDefinition.LONG.equals(dataType)) + { + result = Long.parseLong(textualValue); + } + else if (DataTypeDefinition.NODE_REF.equals(dataType)) + { + result = (NodeRef.isNodeRef(textualValue)) ? (new NodeRef(textualValue)) : (null); + } + else if (DataTypeDefinition.QNAME.equals(dataType)) + { + result = QName.resolveToQName(getNamespaceService(), textualValue); + } + else if (DataTypeDefinition.CONTENT.equals(dataType)) + { + result = ContentData.createContentProperty(textualValue); + } + else if (DataTypeDefinition.TEXT.equals(dataType) || DataTypeDefinition.MLTEXT.equals(dataType)) + { + result = textualValue; + } + } + catch (NumberFormatException e) + { + throw new AVMBadArgumentException("Value for the '" + propertyName + "' property is invalid! Conversion error: " + e.toString()); + } + } + // TODO: Conversion for other DataTypes + return result; + } + + /** + * @param properties + * @throws JSONException + */ + public void setProperties(Object nativeProperties) throws JSONException + { + JSONObject properties = (JSONObject) nativeProperties; + if ((null != asset) && !asset.isDeleted()) + { + Map currentProperties = getProperties(); + if (null == currentProperties) + { + throw new AVMNotFoundException("No a property found for the current Asset"); + } + QName type = getAssetType(); + DictionaryService dictionaryService = getDictionaryService(); + TypeDefinition typeDefinition = (null != type) ? (dictionaryService.getType(type)) : (null); + if (null != typeDefinition) + { + Map propertyDefinitions = typeDefinition.getProperties(); + for (String key : JSONObject.getNames(properties)) + { + QName qName = QName.resolveToQName(getNamespaceService(), key); + if (ContentModel.PROP_CONTENT.equals(qName)) + { + updatedProperties.clear(); + throw new AVMBadArgumentException("The 'Content' property can't be set with the 'setProperties()' method! Use a 'writeContent()' instead"); + } + PropertyDefinition property = (propertyDefinitions.containsKey(qName)) ? (propertyDefinitions.get(qName)) : (dictionaryService.getProperty(qName)); + if (null != property) + { + // TODO: Maybe are multi-valued properties operable? + if (property.isProtected() || property.isMultiValued()) + { + updatedProperties.clear(); + throw new AVMBadArgumentException("The '" + key + "' property is not updatable"); + } + Object associatedValue = properties.get(key); + qName = completeContentModelQName(qName); + currentProperties.put(qName.toPrefixString(), (null != associatedValue) ? (associatedValue.toString()) : (null)); + updatedProperties.add(property.getName()); + } + else + { + updatedProperties.clear(); + throw new AVMNotFoundException("The '" + key + "' property definition can't be found"); + } + } + props = currentProperties; + } + } + } + + private QName completeContentModelQName(QName qName) + { + if (qName.getLocalName().equals(qName.getPrefixString()) && NamespaceService.CONTENT_MODEL_1_0_URI.equals(qName.getNamespaceURI())) + { + DictionaryNamespaceComponent service = (DictionaryNamespaceComponent) getSandbox().getWebproject().getWebProjects().getServiceRegistry().getService(NAMESPACE_SERVICE); + return QName.createQName(NamespaceService.CONTENT_MODEL_PREFIX, qName.getLocalName(), service); + } + return qName; + } + + private QName getAssetType() + { + final NodeRef assetNodeRef = AVMNodeConverter.ToNodeRef(asset.getSandboxVersion(), asset.getAvmPath()); + final NodeService nodeService = getNodeService(); + return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public QName doWork() throws Exception + { + RetryingTransactionHelper helper = getSandbox().getWebproject().getWebProjects().getServiceRegistry().getRetryingTransactionHelper(); + return helper.doInTransaction(new RetryingTransactionCallback() + { + public QName execute() throws Throwable + { + return nodeService.getType(assetNodeRef); + } + }); + } + }, AuthenticationUtil.getFullyAuthenticatedUser()); + } + + /** + * Updates a content of a current Asset + * + * @param content {@link String} value which represents new textual content + * @return true if a content has been set without errors + */ + public boolean writeContent(String content) + { + NodeRef assetNodeRef = AVMNodeConverter.ToNodeRef(asset.getSandboxVersion(), asset.getAvmPath()); + ContentService contentService = getSandbox().getWebproject().getWebProjects().getServiceRegistry().getContentService(); + ContentWriter writer = contentService.getWriter(assetNodeRef, ContentModel.PROP_CONTENT, true); + if ((null != writer) && (null != content)) + { + writer.setMimetype("text/plain"); + writer.setEncoding("UTF-8"); + writer.putContent(content); + return true; + } + return false; + } + + /** + * Updates a content of the current Asset + * + * @param content a {@link Content} value which represents new content + * @return true if a content has been set without errors + */ + public boolean writeContent(Content content) + { + NodeRef assetNodeRef = AVMNodeConverter.ToNodeRef(asset.getSandboxVersion(), asset.getAvmPath()); + ContentService contentService = getSandbox().getWebproject().getWebProjects().getServiceRegistry().getContentService(); + ContentWriter writer = contentService.getWriter(assetNodeRef, ContentModel.PROP_CONTENT, true); + if ((null != writer) && (null != content)) + { + writer.setMimetype(content.getMimetype()); + writer.setEncoding(content.getEncoding()); + writer.putContent(content.getInputStream()); + return true; + } + return false; + } + + /** + * Returns textual representation of the Asset content + * + * @return content as a text + * @throws ContentIOException + * @throws IOException + */ + public String getContent() throws ContentIOException, IOException + { + NodeRef assetNodeRef = AVMNodeConverter.ToNodeRef(asset.getSandboxVersion(), asset.getAvmPath()); + ContentService contentService = getSandbox().getWebproject().getWebProjects().getServiceRegistry().getContentService(); + ContentReader reader = contentService.getReader(assetNodeRef, ContentModel.PROP_CONTENT); + return (null != reader) ? (reader.getContentString()) : (null); + } + /** * Submit this asset to staging * @param submitLabel @@ -338,4 +576,14 @@ public class Asset implements Serializable { return getSandbox().getWebproject().getWebProjects().getNamespaceService(); } + + private NodeService getNodeService() + { + return getSandbox().getWebproject().getWebProjects().getServiceRegistry().getNodeService(); + } + + private DictionaryService getDictionaryService() + { + return getSandbox().getWebproject().getWebProjects().getServiceRegistry().getDictionaryService(); + } } diff --git a/source/java/org/alfresco/wcm/webproject/script/WebProjects.java b/source/java/org/alfresco/wcm/webproject/script/WebProjects.java index b1d2d9a4d5..0741989cb2 100644 --- a/source/java/org/alfresco/wcm/webproject/script/WebProjects.java +++ b/source/java/org/alfresco/wcm/webproject/script/WebProjects.java @@ -62,7 +62,12 @@ public class WebProjects extends BaseScopableProcessorExtension { this.serviceRegistry = serviceRegistry; } - + + public ServiceRegistry getServiceRegistry() + { + return serviceRegistry; + } + /** * Set the wcm web project service * diff --git a/source/test-resources/jbpm-test/test-hibernate-cfg.properties b/source/test-resources/jbpm-test/test-hibernate-cfg.properties index c22bdc7d69..5a45d7dc0d 100644 --- a/source/test-resources/jbpm-test/test-hibernate-cfg.properties +++ b/source/test-resources/jbpm-test/test-hibernate-cfg.properties @@ -9,7 +9,7 @@ hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect hibernate.hbm2ddl.auto=update hibernate.jdbc.use_streams_for_binary=true hibernate.show_sql=false -hibernate.cache.use_query_cache=true +hibernate.cache.use_query_cache=false hibernate.max_fetch_depth=10 hibernate.cache.provider_class=org.alfresco.repo.cache.InternalEhCacheManagerFactoryBean hibernate.cache.use_second_level_cache=true