From da8bdafbccb120b53fa52e1f6bbbfcb98d7a5f18 Mon Sep 17 00:00:00 2001 From: Jan Vonka Date: Mon, 18 Mar 2013 09:23:29 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20BRANCHES/DEV/CONV=5FHEAD=20to=20HEAD:?= =?UTF-8?q?=20=20=20=2047880:=20Create=20branch=20for=20Cloud=20Convergenc?= =?UTF-8?q?e=20from=20the=20latest=20state=20of=20HEAD=20(Revision=2047874?= =?UTF-8?q?)=20=20=20=2047886:=20Merged=20BRANCHES/DEV/CONV=5FV413=20to=20?= =?UTF-8?q?BRANCHES/DEV/CONV=5FHEAD:=20=20=20=20=20=20=20=20=2033052:=20(R?= =?UTF-8?q?ECORD=20ONLY)=20Branch=20for=20Enterprise=204.0=20service=20pac?= =?UTF-8?q?k=20development=20=20=20=20=20=20=20=20=2038002:=20(RECORD=20ON?= =?UTF-8?q?LY)=20Create=20branch=20for=204.1=20Enterprise=20releases,=20ba?= =?UTF-8?q?sed=20on=204.0.2=20=20=20=20=20=20=20=20=2038003:=20(RECORD=20O?= =?UTF-8?q?NLY)=20Update=20version=20to=204.1.0=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?38079:=20(RECORD=20ONLY)=20Updated=20schema=20version=20to=2051?= =?UTF-8?q?00=20=20=20=20=20=20=20=20=2038536:=20(RECORD=20ONLY)=20Merged?= =?UTF-8?q?=20V4.1-BUG-FIX=20to=20V4.1=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=2038219:=20ALF-14674:=20DOS=20voodoo=20to=20make=20start=5F?= =?UTF-8?q?deployment.bat=20work,=20as=20installed=20by=20Bitrock=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=2038344:=20ALF-14674:=20Deployme?= =?UTF-8?q?nt=20installer=20still=20doesn't=20work=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20-=20Use=20${installdir.escape=5Fbackslashes}?= =?UTF-8?q?=20instead=20of=20${installdir}=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2038471:=20ALF-14674:=20Deployment=20installer=20stil?= =?UTF-8?q?l=20doesn't=20work=20=20=20=20=20=20=20=20=20=20=20=20=20=20-?= =?UTF-8?q?=20Correction=20to=20use=20of=20${installdir.escape=5Fbackslash?= =?UTF-8?q?es}=20=20=20=20=20=20=20=20=2039519:=20(RECORD=20ONLY)=20Merged?= =?UTF-8?q?=20PATCHES/V4.0.2=20to=20V4.1=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=2038899:=20ALF-15005:=20Merged=20V4.0-BUG-FIX=20to=20PAT?= =?UTF-8?q?CHES/V4.0.2=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=203?= =?UTF-8?q?7920:=20ALF-13816:=20Permission=20Denied=20on=20web-client=20br?= =?UTF-8?q?owsing=20if=20parent=20does=20not=20inherit=20permissions=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20-=20FileF?= =?UTF-8?q?olderService=20getNamePath()=20now=20performs=20toFileInfo()=20?= =?UTF-8?q?as=20SystemUser.=20=20=20=20=20=20=20=20=20=20=20=20=20=2038900?= =?UTF-8?q?:=20ALF-15005:=20Merged=20V4.1-BUG-FIX=20to=20PATCHES/V4.0.2=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2038549:=20ALF-11?= =?UTF-8?q?861:=20Maintain=20the=20same=20defuault=20root=20of=20WebDav=20?= =?UTF-8?q?for=20Alfresco=204.0=20as=20was=20in=20pre-4.0=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20Removed=20overridi?= =?UTF-8?q?ng=20protocols.rootPath=20property=20from=20installer=20and=20e?= =?UTF-8?q?nterprise=20overlay=20versions=20of=20alfresco-global.propertie?= =?UTF-8?q?s=20so=20that=20correct=20setting=20in=20repository.properties?= =?UTF-8?q?=20is=20used.=20=20=20=20=20=20=20=20=20=20=20=20=20=2039494:?= =?UTF-8?q?=20ALF-15213=20/=20ALF-15170:=20Can't=20change=20folder=20permi?= =?UTF-8?q?ssions=20in=20Private=20or=20Public-moderated=20sites=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20-=20Fix=20by=20Dmitry?= =?UTF-8?q?=20V=20=20=20=20=20=20=20=20=2044843:=20(RECORD=20ONLY)=20Creat?= =?UTF-8?q?ed=20hotfix=20branch=20off=20V4.1=20build=20372=20revision=2044?= =?UTF-8?q?743=20(candidate=204.1.2=20release)=20=20=20=20=20=20=20=20=204?= =?UTF-8?q?5708:=20(RECORD=20ONLY)=20Merged=20PATCHES/V4.1.2=20to=20PATCHE?= =?UTF-8?q?S/V4.1.3=20=20=20=20=20=20=20=20=20=20=20=20=20=2045570:=20Merg?= =?UTF-8?q?ed=20V3.4-BUG-FIX=20to=20PATCHES/V4.1.2=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=2043939:=20ALF-17197=20/=20ALF-16917:?= =?UTF-8?q?=20Merged=20PATCHES/V3.4.11=20to=20V3.4-BUG-FIX=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2043896:=20MNT-198:?= =?UTF-8?q?=20Activity=20feeds=20get=20not=20generated=20in=20private=20si?= =?UTF-8?q?tes=20for=20added=20files=20if=20username=20in=20LDAP-AD=20cont?= =?UTF-8?q?ains=20uppercase=20letters=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20-=20Now=20we=20can=20cope=20with=20a=20?= =?UTF-8?q?runAs=20where=20the=20username=20is=20in=20the=20wrong=20case?= =?UTF-8?q?=20=20=20=20=20=20=20=20=2045714:=20(RECORD=20ONLY)=20Merged=20?= =?UTF-8?q?BRANCHES/DEV/V4.1-BUG-FIX=20to=20PATCHES/DEV/V4.1.3=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=2045513:=20MNT-279:=20Use=20bina?= =?UTF-8?q?ry=20search=20in=20cached=20authority=20search=20to=20cut=20dow?= =?UTF-8?q?n=20search=20time=20when=20a=20group=20contains=20an=20astronom?= =?UTF-8?q?ical=20number=20of=20authorities=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20-=20Experimental=20fix=20to=20cut=20down=20on=20?= =?UTF-8?q?severe=20profiling=20hit=20=20=20=20=20=20=20=20=2045715:=20(RE?= =?UTF-8?q?CORD=20ONLY)=20Merged=20BRANCHES/DEV/V4.1-BUG-FIX=20to=20PATCHE?= =?UTF-8?q?S/V4.1.3=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2044848:=20F?= =?UTF-8?q?ix=20for=20=20=20=20=20ALF-17178=20SolrLuceneAnalyser.findAnaly?= =?UTF-8?q?ser=20generating=20InavlidQNameExceptions=20wher=20they=20are?= =?UTF-8?q?=20easily=20protected.=20=20=20=20=20=20=20=20=2046188:=20(RECO?= =?UTF-8?q?RD=20ONLY)=20Merged=20BRANCHES/DEV/V4.1-BUG-FIX=20to=20PATCHES/?= =?UTF-8?q?V4.1.3=20=20=20=20=20=20=20=20=20=20=20=20=20=2046014:=20Fix=20?= =?UTF-8?q?for=20ALF-17732=20-=20SWF=20files=20are=20considered=20insecure?= =?UTF-8?q?=20content=20and=20should=20not=20be=20displayed=20directly=20i?= =?UTF-8?q?n=20the=20browser.=20=20=20=20=20=20=20=20=20=20=20=20=20=20461?= =?UTF-8?q?60:=20Fix=20for=20ALF-17759=20-=20HTML=20files=20are=20stripped?= =?UTF-8?q?=20from=20metadata=20and=20style=20information=20after=20they?= =?UTF-8?q?=20are=20uploaded.=20=20=20=20=20=20=20=20=20=20=20=20=20=20461?= =?UTF-8?q?65:=20Fix=20for=20ALF-17787=20-=20Site=20Members=20'All=20Membe?= =?UTF-8?q?rs'=20link=20should=20not=20run=20query=20immediately=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=2046169:=20Fix=20for=20ALF-17787?= =?UTF-8?q?=20-=20Site=20Members=20'All=20Members'=20link=20should=20not?= =?UTF-8?q?=20run=20query=20immediately=20-=20missing=20file=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=2046186:=20Fix=20for=20ALF-17786=20?= =?UTF-8?q?-=20Site=20dashboard=20page=20issues=20too=20many=20requests=20?= =?UTF-8?q?(Site=20Members=20dashlet=20issues=20avatar=20requests=20when?= =?UTF-8?q?=20it=20doesn't=20need=20too)=20=20=20=20=20=20=20=20=2046242:?= =?UTF-8?q?=20(RECORD=20ONLY)=20Merged=20BRANCHES/DEV/V4.1-BUG-FIX=20to=20?= =?UTF-8?q?PATCHES/V4.1.3:=20=20=20=20=20=20=20=20=20=20=20=20=20=2046184:?= =?UTF-8?q?=20Refactoring=20a=20test=20class=20to=20use=20JUnit=20Rules=20?= =?UTF-8?q?-=20as=20part=20of=20attempt=20to=20reproduce=20ALF-17797.=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=2046192:=20Enhancement=20t?= =?UTF-8?q?o=20JUnit=20Rule=20TemporaryNodes.java=20as=20required=20by=20f?= =?UTF-8?q?ix=20for=20ALF-17797.=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?46194:=20Fix=20for=20ALF-17797.=20AddFailedThumbnailActionExecu?= =?UTF-8?q?ter=20is=20failing.=20=20=20=20=20=20=20=20=2046710:=20(RECORD?= =?UTF-8?q?=20ONLY)=20Create=20branch=20for=20Cloud=20Convergence=20from?= =?UTF-8?q?=20the=20latest=20state=20of=204.1.3=20(RC5,=20Build=2085,=20Re?= =?UTF-8?q?vision=2046648)=20=20=20=2047908:=20Merged=20from=20DEV/CONV=5F?= =?UTF-8?q?V143=20to=20DEV/CONV=5FHEAD=20=20=20=20=20=20=20=20=2046788:=20?= =?UTF-8?q?Merged=20from=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5F?= =?UTF-8?q?V413=20=20=20=20=20=20=20=20=20=20=20=20Merged=20BRANCHES/DEV/T?= =?UTF-8?q?HOR1=20to=20BRANCHES/DEV/CLOUD1:=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=2030323:=20(RECORD=20ONLY)=20Merged=20HEAD=20to=20?= =?UTF-8?q?BRANCHES/DEV/THOR1:=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2030171:=20ALF-9613:=20caching=20content=20store.=20V?= =?UTF-8?q?arious=20improvements=20and=20bug=20fixes.=20Including:=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=2030325:=20THOR-114:=20S3?= =?UTF-8?q?=20content=20store=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?30326:=20THOR-128:=20S3=20content=20store=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=2030333:=20THOR-139=20F101:=20Get=20accou?= =?UTF-8?q?nt=20for=20user=20e-mail=20id=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2030335:=20Merge=20from=20THOR0=20to=20THOR1=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20r30274:=20THOR-135?= =?UTF-8?q?=20is=20email=20address=20accepted=20by=20Alfresco=3F=20Part=20?= =?UTF-8?q?One.=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030340:=20THOR-?= =?UTF-8?q?99:=20Thor=20module=20-=20enable=20tests=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=2030341:=20Removing=20duplicate=20account?= =?UTF-8?q?-service-context.xml=20file.=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2030343:=20Merge=20THOR0=20to=20THOR1=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=2030339:=20Test=20email=20?= =?UTF-8?q?singup=20in=20Share=20complete=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=2030338:=20New=20form=20runtime=20f?= =?UTF-8?q?eatures:=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20-=20Yellow=20background=20is=20displayed=20fo?= =?UTF-8?q?r=20mandatory=20fields=20without=20value=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20-=20Red=20ba?= =?UTF-8?q?ckground=20dis=20displayed=20for=20fields=20with=20validation?= =?UTF-8?q?=20errors=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20-=20Error=20message=20is=20displayed=20in=20a?= =?UTF-8?q?=20balloon=20when=20fields=20with=20error=20has=20focus=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20-=20Using=20balloons=20is=20now=20the=20default=20method=20o?= =?UTF-8?q?f=20displaying=20errors=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20-=20Removed=20balloon=20code?= =?UTF-8?q?=20form=20create=20site=20menu=20since=20its=20now=20handled=20?= =?UTF-8?q?automatically=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20-=20An=20alternative=20to=20balloons=20?= =?UTF-8?q?are=20"error=20containers"=20(div=20with=20clickable=20red=20te?= =?UTF-8?q?xt=20labels=20focusing=20the=20field):=20setErrorContainer(divE?= =?UTF-8?q?l)=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20-=20Its=20possible=20to=20setMultipleErrors(true)?= =?UTF-8?q?=20to=20display=20all=20the=20forms/fields=20errors=20in=20the?= =?UTF-8?q?=20"error=20container"/ballon.=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20-=20Its=20possible=20to?= =?UTF-8?q?=20turn=20of=20the=20balloons=20and=20error=20containers=20comp?= =?UTF-8?q?lete=20by=20setting=20setErrorContainer(null)=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20-=20js=20?= =?UTF-8?q?validation=20handlers=20no=20longer=20needs=20to=20handle=20the?= =?UTF-8?q?=20messages=20OR=20the=20css=20classes=20for=20mandatory=20&=20?= =?UTF-8?q?invalid=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030344:=20Mi?= =?UTF-8?q?ssing=20value=20check=20caused=20js=20undefined=20error=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=2030346:=20Minor=20css=20f?= =?UTF-8?q?orm=20fixes=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030347:?= =?UTF-8?q?=20THOR-126:=20S3=20content=20store=20-=20do=20not=20swallow=20?= =?UTF-8?q?exceptions=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030348:?= =?UTF-8?q?=20THOR-66:=20disable=20unused=20services/features=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=2030349:=20THOR-137=20F88:=20Add?= =?UTF-8?q?=20existing=20external=20user=20(from=20another=20network)=20ch?= =?UTF-8?q?eckpoint=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030350:=20T?= =?UTF-8?q?HOR-135=20Is=20email=20address=20accepted=20by=20Alfresco.=20?= =?UTF-8?q?=20=20=20=20=20=20=20=2046789:=20Merged=20from=20BRANCHES/DEV/C?= =?UTF-8?q?LOUD2=20to=20BRANCHES/DEV/CONV=5FV413=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2035594:=20Fix=20merge=20issue=20=20=20=2047930:=20Me?= =?UTF-8?q?rged=20BRANCHES/DEV/CONV=5FV413=20to=20BRANCHES/DEV/CONV=5FHEAD?= =?UTF-8?q?:=20=20=20=20=20=20=20=20=2046762:=20(RECORD=20ONLY)=20Merged?= =?UTF-8?q?=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2046768:=20(RECORD=20ONLY)=20Merged=20BRANCH?= =?UTF-8?q?ES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2046769:=20(RECORD=20ONLY)=20Merged=20BRANCHES/DEV/CL?= =?UTF-8?q?OUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20?= =?UTF-8?q?=2046778:=20(RECORD=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD2=20to?= =?UTF-8?q?=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20=2046780:?= =?UTF-8?q?=20(RECORD=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCH?= =?UTF-8?q?ES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20=2046786:=20(RECORD?= =?UTF-8?q?=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CON?= =?UTF-8?q?V=5FV413:=20=20=20=20=20=20=20=20=2046791:=20(RECORD=20ONLY)=20?= =?UTF-8?q?Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:?= =?UTF-8?q?=20=20=20=20=20=20=20=20=2046792:=20(RECORD=20ONLY)=20Merged=20?= =?UTF-8?q?BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2046808:=20(RECORD=20ONLY)=20Merged=20BRANCHES/?= =?UTF-8?q?DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20=20?= =?UTF-8?q?=20=20=2046809:=20(RECORD=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD?= =?UTF-8?q?2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20=2046?= =?UTF-8?q?819:=20(RECORD=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BR?= =?UTF-8?q?ANCHES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20=2046829:=20(REC?= =?UTF-8?q?ORD=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/?= =?UTF-8?q?CONV=5FV413:=20=20=20=20=20=20=20=20=2046839:=20(RECORD=20ONLY)?= =?UTF-8?q?=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413?= =?UTF-8?q?:=20=20=20=20=20=20=20=20=2046842:=20(RECORD=20ONLY)=20Merged?= =?UTF-8?q?=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2046844:=20(RECORD=20ONLY)=20Merged=20BRANCH?= =?UTF-8?q?ES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2046846:=20(RECORD=20ONLY)=20Merged=20BRANCHES/DEV/CL?= =?UTF-8?q?OUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20?= =?UTF-8?q?=2046847:=20(RECORD=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD2=20to?= =?UTF-8?q?=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20=2046876:?= =?UTF-8?q?=20(RECORD=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCH?= =?UTF-8?q?ES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20=2046877:=20(RECORD?= =?UTF-8?q?=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CON?= =?UTF-8?q?V=5FV413:=20=20=20=20=20=20=20=20=2046878:=20(RECORD=20ONLY)=20?= =?UTF-8?q?Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:?= =?UTF-8?q?=20=20=20=20=20=20=20=20=2046879:=20(RECORD=20ONLY)=20Merged=20?= =?UTF-8?q?BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2046880:=20(RECORD=20ONLY)=20Merged=20BRANCHES/?= =?UTF-8?q?DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20=20?= =?UTF-8?q?=20=20=2046881:=20(RECORD=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD?= =?UTF-8?q?2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=2047947:=20Merged?= =?UTF-8?q?=20BRANCHES/DEV/CONV=5FV413=20to=20BRANCHES/DEV/CONV=5FHEAD:=20?= =?UTF-8?q?=20=20=20=20=20=20=20=2046737:=20Merged=20BRANCHES/DEV/CLOUD2?= =?UTF-8?q?=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2035288:=20Alfresco=20Cloud=20(from=20BRANCHES/V4.0)?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=2035389:=20Merged=20BRA?= =?UTF-8?q?NCHES/DEV/THOR1=20to=20BRANCHES/DEV/CLOUD1:=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=2030170:=20Thor=20branch?= =?UTF-8?q?=20based=20on=20Swift=20feature=20complete=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=2030185:=20Merged=20BRANCHES/?= =?UTF-8?q?DEV/THOR0=20to=20BRANCHES/DEV/THOR1:=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2028973:=20THOR-1:?= =?UTF-8?q?=20verify=20ability=20to=20create=20DB=20schema=20programatical?= =?UTF-8?q?ly=20on=20AWS=20RDS=20(for=20MySQL=20&=20Oracle)=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2028999:?= =?UTF-8?q?=20THOR-3:=20Tenant=20Routing=20Data=20Source=20(dynamic=20tena?= =?UTF-8?q?nt-aware=20DB=20connection=20pools)=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029022:=20THOR-1:?= =?UTF-8?q?=20verify=20ability=20to=20create=20DB=20schema=20programatical?= =?UTF-8?q?ly=20on=20AWS=20RDS=20(for=20MySQL=20&=20Oracle)=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029031:?= =?UTF-8?q?=20THOR-1:=20verify=20ability=20to=20create=20DB=20schema=20pro?= =?UTF-8?q?gramatically=20on=20AWS=20RDS=20(for=20MySQL=20&=20Oracle)=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030186:=20?= =?UTF-8?q?Merged=20BRANCHES/DEV/THOR0=20to=20BRANCHES/DEV/THOR1:=20(3=20c?= =?UTF-8?q?onflicts=20resolved)=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=2029116:=20THOR-3:=20Tenant=20Routi?= =?UTF-8?q?ng=20Data=20Source=20(dynamic=20tenant-aware=20DB=20connection?= =?UTF-8?q?=20pools)=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2029174:=20THOR-24=20Set=20up=20new=20Alfresco?= =?UTF-8?q?=20AMP=20module=20project.=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=2029186:=20THOR-25=20Copy=20a?= =?UTF-8?q?nd=20refactor=20Account=20Service=20from=20SambaJAM=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20291?= =?UTF-8?q?93:=20ImporterComponent=20-=20prep=20for=20THOR-7=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20291?= =?UTF-8?q?98:=20THOR-7:=20=20Tenant=20Service=20API=20-=20Create=20Tenant?= =?UTF-8?q?=20(using=20separate=20DB=20schema)=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029204:=20THOR-29?= =?UTF-8?q?=20Account=20Type=20Registry=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=2029234:=20THOR-7:=20=20Te?= =?UTF-8?q?nant=20Service=20API=20-=20Create=20Tenant=20(using=20separate?= =?UTF-8?q?=20DB=20schema)=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=2029246:=20THOR-7:=20=20Tenant=20Service?= =?UTF-8?q?=20API=20-=20Create=20Tenant=20(using=20separate=20DB=20schema)?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=2029251:=20THOR-30=20Added=20AccountDAO=20interface=20al?= =?UTF-8?q?ong=20with=20two=20implementations:=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20Accoun?= =?UTF-8?q?tDAOImpl=20(not=20implemented)=20which=20will=20manage=20Accoun?= =?UTF-8?q?t=20data=20in=20an=20RDB=20via=20iBatis.=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?AccountDAO=5FInMemory=20which=20manages=20AccountInfo=20in=20si?= =?UTF-8?q?mple=20HashMaps=20for=20testing=20purposes=20only.=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20292?= =?UTF-8?q?58:=20THOR-28=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=2029259:=20Addendum=20to=20THOR-25.=20Mov?= =?UTF-8?q?ed=20account-service=20spring=20config=20into=20a=20subfolder.?= =?UTF-8?q?=20(trivial)=20=20=20=20=20=20=20=20=20=20=20=20=20=2035393:=20?= =?UTF-8?q?(RECORD=20ONLY)=20Merged=20BRANCHES/DEV/THOR1=20to=20BRANCHES/D?= =?UTF-8?q?EV/CLOUD1:=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20-=20fix=20up=20classpath=20(remove=20mybatis=201.0.0?= =?UTF-8?q?=20->=201.0.1=20and=20chemistry=200.4.0=20->=200.6.0)=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=2035411:=20(RECORD=20ONLY)=20Mer?= =?UTF-8?q?ged=20BRANCHES/DEV/V4.0-BUG-FIX=20to=20BRANCHES/DEV/CLOUD1:=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2035409:=20?= =?UTF-8?q?Merged=20HEAD=20to=20BRANCHES/DEV/V4.0-BUG-FIX:=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2035399:?= =?UTF-8?q?=20ALF-12874:=20Schema=20reference=20files=20are=20out=20of=20d?= =?UTF-8?q?ate.=20=20=20=20=20=20=20=20=20=20=20=20=20=2035452:=20(RECORD?= =?UTF-8?q?=20ONLY)=20Merged=20BRANCHES/DEV/V4.0-BUG-FIX=20to=20BRANCHES/D?= =?UTF-8?q?EV/CLOUD1:=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=2034219:=20Merged=20BRANCHES/DEV/THOR1=20to=20BRANCHES/D?= =?UTF-8?q?EV/V4.0-BUG-FIX:=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=2032096:=20THOR-429:=20Fix=20"MT:=20Th?= =?UTF-8?q?umbnail=20+=20Preview=20are=20not=20updated=20(after=20uploadin?= =?UTF-8?q?g=20new=20version)"=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=2032125:=20THOR-429:=20Fix=20"MT:?= =?UTF-8?q?=20Thumbnail=20+=20Preview=20are=20not=20updated=20(after=20upl?= =?UTF-8?q?oading=20new=20version)"=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2034220:=20Minor:=20follow-on=20to=20r34219?= =?UTF-8?q?=20(ALF-11563)=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2034747:=20ALF-13262:=20adding=20missing=20indexes=20?= =?UTF-8?q?for=20new=20schema's=20(activiti-schema=20create)=20+=20schema?= =?UTF-8?q?=20patch=20for=20existing=20schema=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=2035417:=20Merged=20BRANCHES/DEV?= =?UTF-8?q?/THOR0=20to=20BRANCHES/DEV/V4.0-BUG-FIX:=20(THOR-6=20/=20ALF-13?= =?UTF-8?q?755)=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=2029356:=20THOR-6:=20MT=20is=20configured=20(but?= =?UTF-8?q?=20not=20enabled)=20by=20default=20-=20will=20be=20auto-enabled?= =?UTF-8?q?=20when=20first=20tenant=20is=20created=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029455:=20THOR-6?= =?UTF-8?q?:=20build=20test/fix=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=2029471:=20THOR-6:=20build=20test/f?= =?UTF-8?q?ix=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=203?= =?UTF-8?q?5423:=20Merged=20BRANCHES/DEV/THOR0=20to=20BRANCHES/DEV/V4.0-BU?= =?UTF-8?q?G-FIX:=20(THOR-4=20/=20ALF-13756)=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=2029500:=20THOR-4:=20Re?= =?UTF-8?q?place=20Tenant=20attributes=20with=20Tenant=20table=20(alf=5Fte?= =?UTF-8?q?nant)=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=2029501:=20THOR-4:=20Replace=20Tenant=20attributes?= =?UTF-8?q?=20with=20Tenant=20table=20(alf=5Ftenant)=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029503:=20THO?= =?UTF-8?q?R-4:=20Replace=20Tenant=20attributes=20with=20Tenant=20table=20?= =?UTF-8?q?(alf=5Ftenant)=20=20=20=2047949:=20Merged=20HEAD=20to=20BRANCHE?= =?UTF-8?q?S/DEV/CONV=5FHEAD:=20=20=20=20=20=20=20=20=2047914:=20Merge=20f?= =?UTF-8?q?ix=20for=20org.alfresco.repo.cache.AbstractAsynchronouslyRefres?= =?UTF-8?q?hedCache=20R=2046078,=2046079,=2046121=20=20=20=2047958:=20M?= =?UTF-8?q?erged=20BRANCHES/DEV/CONV=5FV413=20to=20BRANCHES/DEV/CONV=5FHEA?= =?UTF-8?q?D:=20=20=20=20=20=20=20=20=2046746:=20Merged=20BRANCHES/DEV/CLO?= =?UTF-8?q?UD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=2035455:=20Merged=20BRANCHES/DEV/THOR1=20to=20BRAN?= =?UTF-8?q?CHES/DEV/CLOUD1:=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2030187:=20Merged=20BRANCHES/DEV/THOR0=20to=20BRANCHE?= =?UTF-8?q?S/DEV/THOR1:=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2029260:=20THOR:=20Initial=20Tenant=20Admin?= =?UTF-8?q?=20Service=20REST=20API=20-=20create,=20delete,=20get=20(list)?= =?UTF-8?q?=20web=20scripts=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=2029356:=20THOR-6:=20MT=20is=20configu?= =?UTF-8?q?red=20by=20default=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=2029366:=20THOR-59:=20selectively=20di?= =?UTF-8?q?sable=20certain=20test=20suites=20(for=20THOR=20dev=20build=20p?= =?UTF-8?q?lan)=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=2029377:=20THOR-59:=20selectively=20disable=20cert?= =?UTF-8?q?ain=20test=20suites=20(for=20THOR=20dev=20build=20plan)=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=2029398:=20Refactoring=20of=20code=20to=20remove=20deprecation?= =?UTF-8?q?=20warnings.=20Replaced=20lots=20of=20object.field=20accesses?= =?UTF-8?q?=20with=20object.getField()=20calls.Trivial=20changes,=20but=20?= =?UTF-8?q?with=20so=20many=20warnings=20I=20can't=20see=20the=20wood=20fo?= =?UTF-8?q?r=20the=20trees.=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=2029400:=20THOR-59:=20selectively=20di?= =?UTF-8?q?sable=20certain=20test=20suites=20(for=20THOR=20dev=20build=20p?= =?UTF-8?q?lan)=20=20=20=20=20=20=20=20=20=20=20=20=20=2035456:=20Merged?= =?UTF-8?q?=20BRANCHES/DEV/THOR1=20to=20BRANCHES/DEV/CLOUD1:=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030188:=20Merged=20?= =?UTF-8?q?BRANCHES/DEV/THOR0=20to=20BRANCHES/DEV/THOR1:=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029442:=20?= =?UTF-8?q?THOR-59:=20selectively=20disable=20certain=20test=20suites=20(f?= =?UTF-8?q?or=20THOR=20dev=20build=20plan)=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=2029453:=20THOR-59:=20s?= =?UTF-8?q?electively=20disable=20certain=20test=20suites=20(for=20THOR=20?= =?UTF-8?q?dev=20build=20plan)=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=2029455:=20THOR-76:=20track=20THOR?= =?UTF-8?q?=20build=20test=20failures=20and=20fix-up=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029471:=20THO?= =?UTF-8?q?R-76:=20track=20THOR=20build=20test=20failures=20and=20fix-up?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=2035459:=20Merged=20BRA?= =?UTF-8?q?NCHES/DEV/THOR1=20to=20BRANCHES/DEV/CLOUD1:=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=2030189:=20Merged=20BRANCH?= =?UTF-8?q?ES/DEV/THOR0=20to=20BRANCHES/DEV/THOR1:=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029473:=20Prelim?= =?UTF-8?q?inary=20checkin=20for=20THOR-44.=20Created=20placeholder=20inte?= =?UTF-8?q?rface/impl/spring=20config=20for=20a=20new=20UserService.=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=2029497:=20THOR-76:=20track=20THOR=20build=20test=20failures?= =?UTF-8?q?=20and=20fix-up=20(=20LicenseComponentTest)=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029500:=20?= =?UTF-8?q?THOR-4:=20Replace=20Tenant=20attributes=20with=20Tenant=20table?= =?UTF-8?q?=20(alf=5Ftenant)=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=2029501:=20THOR-4:=20Replace=20Tenant?= =?UTF-8?q?=20attributes=20with=20Tenant=20table=20(alf=5Ftenant)=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?29503:=20THOR-4:=20Replace=20Tenant=20attributes=20with=20Tenan?= =?UTF-8?q?t=20table=20(alf=5Ftenant)=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=2029511:=20THOR-59:=20selecti?= =?UTF-8?q?vely=20disable=20certain=20test=20suites=20(for=20THOR=20dev=20?= =?UTF-8?q?build=20plan)=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=2029512:=20Adding=20a=20new=20JUnit4=20te?= =?UTF-8?q?st=20class=20with=20an=20@Ignore'd=20test=20in=20it=20-=20to=20?= =?UTF-8?q?see=20how=20Bamboo=20reports=20these.=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029514:=20THOR:?= =?UTF-8?q?=20Initial=20Tenant=20Admin=20Service=20REST=20API=20-=20create?= =?UTF-8?q?,=20delete,=20list=20web=20scripts=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029515:=20THOR-59:?= =?UTF-8?q?=20selectively=20disable=20certain=20test=20suites=20(for=20THO?= =?UTF-8?q?R=20dev=20build=20plan)=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=2029521:=20THOR-79=20-=20mark=20?= =?UTF-8?q?AVM=20sitestore=20as=20unindexed=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2035461:=20Merged=20BRANCHES/DEV/THOR1=20to=20BRANCHE?= =?UTF-8?q?S/DEV/CLOUD1:=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=2030190:=20Merged=20BRANCHES/DEV/THOR0=20to=20BRANCHES/D?= =?UTF-8?q?EV/THOR1:=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2029533:=20THOR-59:=20exclude=20certain=20N/A?= =?UTF-8?q?=20tests=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2029555:=20THOR-76:=20track=20THOR=20build=20te?= =?UTF-8?q?st=20failures=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=2029630:=20Added=20ant=20build=20targets?= =?UTF-8?q?=20for=20Cloud=20Module=20and=20a=20new=20executable=20for=20th?= =?UTF-8?q?e=20Alfresco=20devenv.=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=2029664:=20THOR-76:=20exclude=20?= =?UTF-8?q?system=20test=20suites=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=2029667:=20THOR-64:=20add=20init?= =?UTF-8?q?ial=20support=20for=20tenant=20routing=20data=20source=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?29676:=20THOR-76:=20exclude=20intermittent=20ActionTrackingServ?= =?UTF-8?q?iceImplTest=20(pending=20ALF-9773=20&=20ALF-9774)=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20296?= =?UTF-8?q?77:=20THOR-80:=20MT-aware=20S3=20content=20store=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029678:?= =?UTF-8?q?=20THOR-80:=20MT-aware=20S3=20content=20store=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029680:=20?= =?UTF-8?q?THOR-80:=20MT-aware=20S3=20content=20store=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029693:=20THO?= =?UTF-8?q?R-80:=20MT-aware=20S3=20content=20store=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029694:=20THOR-8?= =?UTF-8?q?0:=20MT-aware=20S3=20content=20store=20=20=20=2047959:=20CONV?= =?UTF-8?q?=5FHEAD:=20CLOUD-1348=20-=20comment=20back=20in=20MultiTDemoTes?= =?UTF-8?q?t.testDeleteAllTenants=20=20=20=2047967:=20Merged=20BRANCHES/DE?= =?UTF-8?q?V/CONV=5FV413=20to=20BRANCHES/DEV/CONV=5FHEAD:=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2046748:=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCH?= =?UTF-8?q?ES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20=20=20=20=20=20=2035?= =?UTF-8?q?464:=20Merged=20BRANCHES/DEV/THOR1=20to=20BRANCHES/DEV/CLOUD1:?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030195:?= =?UTF-8?q?=20Merged=20BRANCHES/DEV/THOR0=20to=20BRANCHES/DEV/THOR1:=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=2029774:=20Refactor=20Account=20DAO=20and=20Service.=20Boost?= =?UTF-8?q?=20Tests.=20Add=20appropriate=20headers.=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029776:=20THO?= =?UTF-8?q?R-76:=20exclude=20intermittent=20ActionTrackingServiceImplTest?= =?UTF-8?q?=20(pending=20ALF-9773=20&=20ALF-9774)=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029795:=20Implem?= =?UTF-8?q?ented=20MyBatis-backed=20Account=20DAO:=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029817:=20Move?= =?UTF-8?q?=20(and=20rename)=20user=20service=20from=20repository=20to=20t?= =?UTF-8?q?hor=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20301?= =?UTF-8?q?96:=20Merged=20BRANCHES/DEV/THOR0=20to=20BRANCHES/DEV/THOR1:=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=2029827:=20THOR-69:=20TenantAdminDAO=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029832:=20THO?= =?UTF-8?q?R-78:=20fix=20tenantEntityCache=20(shared)=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029834:=20THO?= =?UTF-8?q?R-111:=20experimental=20config=20option=20for=20S3=20content=20?= =?UTF-8?q?store=20to=20support=20flat=20root=20(ie.=20all=20tenant=20file?= =?UTF-8?q?s=20in=20single=20folder)=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=2029856:=20THOR=20updates=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=2029857:=20THOR-76:=20exclude=20build=20components/projects?= =?UTF-8?q?=20=20=20=20=20=20=20=20=2046761:=20Merged=20BRANCHES/DEV/CLOUD?= =?UTF-8?q?2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=2035478:=20Merged=20BRANCHES/DEV/THOR1=20to=20BRAN?= =?UTF-8?q?CHES/DEV/CLOUD1:=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2030198:=20Merged=20BRANCHES/DEV/THOR0=20to=20BRANCHE?= =?UTF-8?q?S/DEV/THOR1:=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2029869:=20THOR-92.=20BPMN2.0=20workflow=20d?= =?UTF-8?q?efinition=20for=20account=20self-signup.=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029871:=20THO?= =?UTF-8?q?R-93.=20REST=20API=20for=20self=20signup=20(and=20miscellaneous?= =?UTF-8?q?=20related=20items).=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=2029882:=20THOR-102:=20Faster=20Cre?= =?UTF-8?q?ateTenant=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2029888:=20THOR-95.=20Placeholder=20email=20tem?= =?UTF-8?q?plate=20for=20self-signup.=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=2029889:=20Completion=20of=20?= =?UTF-8?q?THOR-95.=20Placeholder=20emails=20for=20self-signup.=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20Added=20a=20'you've=20already=20registered'=20te?= =?UTF-8?q?mplate.=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2029896:=20THOR-89F100:=20Create=20User=20Found?= =?UTF-8?q?ation=20API=E2=80=A6=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=2029912:=20Fix=20issue=20where=20mo?= =?UTF-8?q?dule=20believed=20it=20was=20still=20executed=20after=20delete?= =?UTF-8?q?=20tenant=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2029940:=20THOR-96.=20First=20cut=20of=20a=20si?= =?UTF-8?q?gnup=20email=20sender=20delegate.=20This=20will=20be=20refined?= =?UTF-8?q?=20later=20-=20probably=20both=20in=20this=20sprint=20and=20the?= =?UTF-8?q?=20next.=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2029966:=20Fixing=20InvitationServiceImplTest?= =?UTF-8?q?=20failing=20tests,=20which=20are=20failing=20because=20the=20e?= =?UTF-8?q?mail=20templates=20are=20not=20there.=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029978:=20THOR-8?= =?UTF-8?q?9:=20Switch=20tenant=20for=20person=20creation=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029982:?= =?UTF-8?q?=20THOR-89:=20Fix=20multi-domain=20account=20creation=20test=20?= =?UTF-8?q?after=20review=20with=20Jan=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=2029983:=20THOR-102:=20Faster?= =?UTF-8?q?=20CreateTenant=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=2029985:=20THOR-90:=20F99=20Is=20email=20?= =?UTF-8?q?address=20already=20registred=20foundation=20API=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029991:?= =?UTF-8?q?=20THOR-99:=20Thor=20module=20build/packaging=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029994:=20?= =?UTF-8?q?Changes=20for=20THOR-92,=20THOR-93=20and=20THOR-96.=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030199:=20Merged=20?= =?UTF-8?q?BRANCHES/DEV/THOR0=20to=20BRANCHES/DEV/THOR1:=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029995:=20?= =?UTF-8?q?THOR-93.=20Use=20the=20proper=20sspring=20config=20in=20the=20t?= =?UTF-8?q?est=20case.=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2029996:=20THOR-99:=20Thor=20module=20build/?= =?UTF-8?q?packaging=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2029997:=20Consolidated=20DaveC's=20EmailAddres?= =?UTF-8?q?sService=20and=20my=20EMailUtil=20into=20a=20single=20feature.?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=2029998:=20Follow-on=20to=20previous=20check-in=20(29997?= =?UTF-8?q?).=20Deletion=20of=20now-unused=20folder.=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030000:=20Bla?= =?UTF-8?q?tant=20attempt=20to=20get=20svn=20r=3D30k.=20Removing=20some=20?= =?UTF-8?q?dead=20config.=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=2030001:=20THOR-96.=20Ensure=20that=20we?= =?UTF-8?q?=20get=20a=20meaningful=20exception=20when=20attempting=20to=20?= =?UTF-8?q?activate=20an=20account=20with=20no=20pending=20workflow=20for?= =?UTF-8?q?=20that=20email.=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=2030036:=20Resolve=20issues=20with=20t?= =?UTF-8?q?enant-independent=20user=20store=20-=20can=20now=20login=20via?= =?UTF-8?q?=20Share=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2030041:=20Package=20and=20auto=20deploy=20of?= =?UTF-8?q?=20license=20with=20Thor=20module=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=2030048:=20Ensure=20tha?= =?UTF-8?q?t=20when=20a=20duplicate=20email=20prevents=20a=20workflow=20fr?= =?UTF-8?q?om=20creating=20an=20account,=20that=20the=20workflow=20still?= =?UTF-8?q?=20ends=20gracefully.=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=2030049:=20Removing=20a=20dead=20cl?= =?UTF-8?q?ass=20that=20I'd=20used=20to=20see=20how=20our=20Bamboo=20handl?= =?UTF-8?q?es=20@Ignore(message=3Dmsg)=20@Test=20annotations.=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20300?= =?UTF-8?q?54:=20THOR-84=20F82:=20List=20Accounts=20Foundation=20API=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=2030067:=20THOR-87=20List=20Accounts=20REST=20API.=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20300?= =?UTF-8?q?69:=20THOR-87.=20Completion=20of=20listAccounts=20REST=20API.?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20Fixed=20the=20problems=20in=20the=20JUnit?= =?UTF-8?q?=20test=20case=20and=20tweaked=20the=20FTL=20slightly.=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?30071:=20Cosmetic=20changes=20as=20part=20of=20THOR-93.=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=2030072:=20Oops.=20Broke=20a=20test=20case.=20Follow-on=20to?= =?UTF-8?q?=20previous=20(30071)=20check-in=20which=20cosmetically=20chang?= =?UTF-8?q?ed=20JSON=20as=20part=20of=20THOR-93.=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030073:=20As=20p?= =?UTF-8?q?art=20of=20THOR-93=20(REST=20API=20signup)=20I=20have=20made=20?= =?UTF-8?q?the=202=20webscripts=20usable=20without=20any=20authentication.?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=2030074:=20Trivial=20fix=20to=20an=20error=20string.=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=2030076:=20THOR-93.=20The=20account-activation.post=20websc?= =?UTF-8?q?ript=20now=20includes=20the=20provided=20workflowInstanceId=20w?= =?UTF-8?q?hen=20identifying=20the=20ongoing=20workflow.=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030077:=20?= =?UTF-8?q?Fix=20Email=20validator=20to=20allow=20for=20example=20domains?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20302?= =?UTF-8?q?02:=20Merged=20BRANCHES/DEV/THOR0=20to=20BRANCHES/DEV/THOR1:=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=2030140:=20Refactor=20of=20account=20signup=20workflow=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=2030142:=20No=20longer=20require=20email=20address=20for=20?= =?UTF-8?q?activation=20step=20of=20sign-up=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=2030143:=20Remove=20use?= =?UTF-8?q?=20of=20task=20query=20in=20account=20signup=20workflow=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=2030146:=20thor-share=20project=20structure=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030147:=20?= =?UTF-8?q?Buildfix=20(removed=20modules=20not=20used=20by=20THOR)=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=2030151:=20Incorporate=20already=20registered=20use=20case=20i?= =?UTF-8?q?nto=20account=20signup=20workflow=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=2030152:=20Finally=20re?= =?UTF-8?q?solve=20license=20loading=20in=20Eclipse=20based=20tests=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030203:=20?= =?UTF-8?q?Merged=20BRANCHES/DEV/THOR0=20to=20BRANCHES/DEV/THOR1:=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?30184:=20Build=20box=20fix=20as=20a=20result=20of=20not=20inclu?= =?UTF-8?q?ding=20certian=20components=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=2030206:=20Fix=20blatant=20merge=20issues?= =?UTF-8?q?=20=20=20=2047972:=20Merged=20BRANCHES/DEV/CONV=5FV413=20to=20B?= =?UTF-8?q?RANCHES/DEV/CONV=5FHEAD:=20=20=20=20=20=20=20=20=2046766:=20Mer?= =?UTF-8?q?ged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=2035497:=20Merged=20BRANCH?= =?UTF-8?q?ES/DEV/THOR0=20to=20BRANCHES/DEV/CLOUD1:=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=2029723:=20THOR-31:=20MT-awar?= =?UTF-8?q?e=20shared=20caches=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=2029749:=20THOR-5:=20MT-aware=20immutable=20single?= =?UTF-8?q?tons=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029?= =?UTF-8?q?762:=20THOR-31:=20MT-aware=20shared=20cache=20=20=20=20=20=20?= =?UTF-8?q?=20=20=2046767:=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/?= =?UTF-8?q?DEV/CONV=5FV413:=20=20=20=20=20=20=20=20=20=20=20=20=20=2035507?= =?UTF-8?q?:=20Merged=20BRANCHES/DEV/THOR1=20to=20BRANCHES/DEV/CLOUD1:=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030237:=20?= =?UTF-8?q?Merged=20BRANCHES/DEV/THOR0=20to=20BRANCHES/DEV/THOR1:=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?29532:=20THOR-79=20-=20add=20ability=20to=20disable=20Lucene=20?= =?UTF-8?q?indexes=20(so=20that=20IndexInfo=20/=20IndexInfoBackup=20files?= =?UTF-8?q?=20are=20not=20created=20per=20store=20per=20tenant)=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?29723:=20THOR-31:=20MT-aware=20shared=20caches=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029749:?= =?UTF-8?q?=20THOR-5:=20MT-aware=20immutable=20singletons=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2029762:?= =?UTF-8?q?=20THOR-31:=20MT-aware=20shared=20cache=20=20=20=2047973:=20CON?= =?UTF-8?q?V=5FHEAD:=20CLOUD-1348=20-=20comment=20back=20in=20MultiTDemoTe?= =?UTF-8?q?st=20tests=20(testNonSharedGroupDeletion=20&=20testSharedGroupD?= =?UTF-8?q?eletion)=20=20=20=2047975:=20CONV=5FHEAD:=20CLOUD-1348=20-=20co?= =?UTF-8?q?mment=20back=20in=20FeedNotifierTest.testFailedNotifications=20?= =?UTF-8?q?=20=20=2047988:=20Merged=20BRANCHES/DEV/CONV=5FV413=20to=20BRAN?= =?UTF-8?q?CHES/DEV/CONV=5FHEAD:=20=20=20=20=20=20=20=20=2046775:=20Merged?= =?UTF-8?q?=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=2035531:=20Merged=20BRANCHES/?= =?UTF-8?q?DEV/THOR1=20to=20BRANCHES/DEV/CLOUD1:=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=2030449:=20F66:=20add=20optio?= =?UTF-8?q?n=20to=20configure=20a=20common=20"contentRootContainerPath"=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030564:?= =?UTF-8?q?=20THOR-156:=20prep=20-=20consolidate=20runAsSystemTenant/runAs?= =?UTF-8?q?PrimaryTenant=20=20=20=20=20=20=20=20=20=20=20=20=20=2035532:?= =?UTF-8?q?=20Merged=20BRANCHES/DEV/THOR1=20to=20BRANCHES/DEV/CLOUD1:=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030777:=20?= =?UTF-8?q?THOR-201:=20temporarily=20comment-out=20MultiTDemoTest.testDele?= =?UTF-8?q?teArchiveAndRestoreContent=20(pending=20fix=20for=20THOR-201)?= =?UTF-8?q?=20=20=20=2048008:=20Merged=20BRANCHES/DEV/CONV=5FV413=20to=20B?= =?UTF-8?q?RANCHES/DEV/CONV=5FHEAD:=20=20=20=20=20=20=20=20=2046844:=20(RE?= =?UTF-8?q?CORD=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV?= =?UTF-8?q?/CONV=5FV413:=20=20=20=20=20=20=20=20=2046895:=20(RECORD=20ONLY?= =?UTF-8?q?)=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV41?= =?UTF-8?q?3:=20=20=20=20=20=20=20=20=2046903:=20(RECORD=20ONLY)=20Merged?= =?UTF-8?q?=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2046907:=20(RECORD=20ONLY)=20Merged=20BRANCH?= =?UTF-8?q?ES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2046922:=20(RECORD=20ONLY)=20Merged=20BRANCHES/DEV/CL?= =?UTF-8?q?OUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20?= =?UTF-8?q?=2046974:=20(RECORD=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD2=20to?= =?UTF-8?q?=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20=2046991:?= =?UTF-8?q?=20(RECORD=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCH?= =?UTF-8?q?ES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20=2046992:=20(RECORD?= =?UTF-8?q?=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CON?= =?UTF-8?q?V=5FV413:=20=20=20=20=20=20=20=20=2046994:=20(RECORD=20ONLY)=20?= =?UTF-8?q?Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:?= =?UTF-8?q?=20=20=20=20=20=20=20=20=2047107:=20(RECORD=20ONLY)=20Merged=20?= =?UTF-8?q?BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2047265:=20(RECORD=20ONLY)=20Merged=20BRANCHES/?= =?UTF-8?q?DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20=20?= =?UTF-8?q?=20=20=2047267:=20(RECORD=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD?= =?UTF-8?q?2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20=2047?= =?UTF-8?q?272:=20(RECORD=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BR?= =?UTF-8?q?ANCHES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20=2047277:=20(REC?= =?UTF-8?q?ORD=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/?= =?UTF-8?q?CONV=5FV413:=20=20=20=20=20=20=20=20=2047284:=20(RECORD=20ONLY)?= =?UTF-8?q?=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413?= =?UTF-8?q?:=20=20=20=20=20=20=20=20=2047286:=20(RECORD=20ONLY)=20Merged?= =?UTF-8?q?=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2047289:=20(RECORD=20ONLY)=20Merged=20BRANCH?= =?UTF-8?q?ES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2047292:=20(RECORD=20ONLY)=20Merged=20BRANCHES/DEV/CL?= =?UTF-8?q?OUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=2048009:=20Merge?= =?UTF-8?q?d=20DEV/CONV=5FV413=20to=20DEV/CONV=5FHEAD=20=20=20=20=20=20=20?= =?UTF-8?q?=20=2046801:=20Merged=20from=20BRANCHES/DEV/CLOUD2=20to=20BRANC?= =?UTF-8?q?HES/DEV/CONV=5FV413=20=20=20=20=20=20=20=20=20=20=20=2035602:?= =?UTF-8?q?=20Merged=20BRANCHES/DEV/THOR1=20to=20BRANCHES/DEV/CLOUD1:=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=2030513:=20Cloud=20Shar?= =?UTF-8?q?e=20module=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030515:?= =?UTF-8?q?=20Fix=20issue=20with=20person=20replication=20between=20tenant?= =?UTF-8?q?s.=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030516:=20Slight?= =?UTF-8?q?=20mod=20to=20email=20validation=20web=20script=20response.=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=2030518:=20Quick=20fix?= =?UTF-8?q?=20for=20workflow=20id=20generation=20in=20sign=20email=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=2030534:=20THOR-163:=20Una?= =?UTF-8?q?ble=20to=20get=20license=20file=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=2030535:=20Fix=20Thor=20build=20process.=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=2030536:=20Refine=20user's=20?= =?UTF-8?q?home=20site=20name=20and=20description=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2030539:=20THOR-96.=20When=20sending=20the?= =?UTF-8?q?=20signup=20email,=20execute=20the=20mail=20action=20asynchrono?= =?UTF-8?q?usly.=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030542:=20Repl?= =?UTF-8?q?ace=20placeholder=20text=20in=20sign-up=20email=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=2030543:=20Account=20Activation=20?= =?UTF-8?q?=20=20=20=20=20=20=20=2046802:=20Merged=20from=20BRANCHES/DEV/C?= =?UTF-8?q?LOUD2=20to=20BRANCHES/DEV/CONV=5FV413=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2035643:=20Merged=20BRANCHES/DEV/THOR1=20to=20BRANCHE?= =?UTF-8?q?S/DEV/CLOUD1:=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030544?= =?UTF-8?q?:=20Account=20activation=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=2030545:=20Account=20activation=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2030550:=20AMP=20build=20targets=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=2030554:=20THOR-94.=20Cloud=20site?= =?UTF-8?q?=20invitation=20workflow.=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=2030555:=20AMP=20build=20targets=20-=20added=20client=20?= =?UTF-8?q?side=20resources=20=20=20=2048011:=20Merged=20BRANCHES/DEV/CONV?= =?UTF-8?q?=5FV413=20to=20BRANCHES/DEV/CONV=5FHEAD:=20=20=20=20=20=20=20?= =?UTF-8?q?=20=2047056:=20(RECORD=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD2?= =?UTF-8?q?=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20=20470?= =?UTF-8?q?87:=20(RECORD=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRA?= =?UTF-8?q?NCHES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20=2047228:=20(RECO?= =?UTF-8?q?RD=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/C?= =?UTF-8?q?ONV=5FV413:=20=20=20=20=20=20=20=20=2047271:=20(RECORD=20ONLY)?= =?UTF-8?q?=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413?= =?UTF-8?q?:=20=20=20=20=20=20=20=20=2047297:=20(RECORD=20ONLY)=20Merged?= =?UTF-8?q?=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2047299:=20(RECORD=20ONLY)=20Merged=20BRANCH?= =?UTF-8?q?ES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2047300:=20(RECORD=20ONLY)=20Merged=20BRANCHES/DEV/CL?= =?UTF-8?q?OUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20?= =?UTF-8?q?=2047301:=20(RECORD=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD2=20to?= =?UTF-8?q?=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20=2047304:?= =?UTF-8?q?=20(RECORD=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCH?= =?UTF-8?q?ES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20=2047328:=20(RECORD?= =?UTF-8?q?=20ONLY)=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CON?= =?UTF-8?q?V=5FV413:=20=20=20=20=20=20=20=20=2047330:=20(RECORD=20ONLY)=20?= =?UTF-8?q?Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:?= =?UTF-8?q?=20=20=20=20=20=20=20=20=2047339:=20(RECORD=20ONLY)=20Merged=20?= =?UTF-8?q?BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20?= =?UTF-8?q?=2048013:=20Merged=20DEV/CONV=5FV413=20to=20DEV/CONV=5FHEAD=20(?= =?UTF-8?q?commiting=20the=20missing=20merge=20info=20for=20r48009)=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2046801:=20Merged=20from=20BRANCHES/DEV/CLOU?= =?UTF-8?q?D2=20to=20BRANCHES/DEV/CONV=5FV413=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=2035602:=20Merged=20BRANCHES/DEV/THOR1=20to=20BRANCHES/D?= =?UTF-8?q?EV/CLOUD1:=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030513:?= =?UTF-8?q?=20Cloud=20Share=20module=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=2030515:=20Fix=20issue=20with=20person=20replication=20b?= =?UTF-8?q?etween=20tenants.=20=20=20=20=20=20=20=20=20=20=20=20=20=20=203?= =?UTF-8?q?0516:=20Slight=20mod=20to=20email=20validation=20web=20script?= =?UTF-8?q?=20response.=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030518:?= =?UTF-8?q?=20Quick=20fix=20for=20workflow=20id=20generation=20in=20sign?= =?UTF-8?q?=20email=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030534:=20T?= =?UTF-8?q?HOR-163:=20Unable=20to=20get=20license=20file=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=2030535:=20Fix=20Thor=20build=20proces?= =?UTF-8?q?s.=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030536:=20Refine?= =?UTF-8?q?=20user's=20home=20site=20name=20and=20description=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=2030539:=20THOR-96.=20When=20sen?= =?UTF-8?q?ding=20the=20signup=20email,=20execute=20the=20mail=20action=20?= =?UTF-8?q?asynchronously.=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20305?= =?UTF-8?q?42:=20Replace=20placeholder=20text=20in=20sign-up=20email=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=2030543:=20Account=20Activ?= =?UTF-8?q?ation=20=20=20=20=20=20=20=20=2046802:=20Merged=20from=20BRANCH?= =?UTF-8?q?ES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2035643:=20Merged=20BRANCHES/DEV/THOR1=20to=20B?= =?UTF-8?q?RANCHES/DEV/CLOUD1:=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=2030544:=20Account=20activation=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=2030545:=20Account=20activation=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=2030550:=20AMP=20build=20targets=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=2030554:=20THOR-94.=20Clou?= =?UTF-8?q?d=20site=20invitation=20workflow.=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=2030555:=20AMP=20build=20targets=20-=20added=20cli?= =?UTF-8?q?ent=20side=20resources=20=20=20=2048015:=20Merged=20DEV/CONV=5F?= =?UTF-8?q?V413=20to=20DEV/CONV=5FHEAD=20=20=20=20=20=20=20=20=2046841:=20?= =?UTF-8?q?Merged=20from=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5F?= =?UTF-8?q?V413=20=20=20=20=20=20=20=20=20=20=20=2035684:=20Merged=20BRANC?= =?UTF-8?q?HES/DEV/THOR1=20to=20BRANCHES/DEV/CLOUD1:=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=2030904:=20(RECORD=20ONLY)=20Merged=20HEA?= =?UTF-8?q?D=20to=20BRANCHES/DEV/THOR1:=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2030270=20=20ALF-9492=20Can=20now=20update?= =?UTF-8?q?=20task=20properties=20through=20the=20Workflow=20JavaScript=20?= =?UTF-8?q?API.=20ALF-10087=20Fixed=20failing=20Multi-tenancy=20tests.=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030288=20=20A?= =?UTF-8?q?LF-9492=20Can=20now=20update=20task=20properties=20through=20th?= =?UTF-8?q?e=20Workflow=20JavaScript=20API.=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=2030309=20=20Fixed=20failing=20MultiTDemo?= =?UTF-8?q?Test=20and=20re-enabled.=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=2030356=20=20ALF-10117:=20JBPM=20workflows=20shoul?= =?UTF-8?q?d=20be=20hidden.=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=2030358=20=20Build=20fix,=20fallout=20from=20ALF-10117?= =?UTF-8?q?=20(JBPM=20workflows=20should=20be=20hidden)=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=2030415=20=20Added=20parseRoo?= =?UTF-8?q?tElement()=20method=20to=20Activiti's=20BPMNParseListener.=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030452=20=20A?= =?UTF-8?q?LF-10276:=20Reject=20flow=20didn't=20set=20bpm=5Fassignee=20pro?= =?UTF-8?q?perty=20properly=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=2030563=20=20Added=20tests=20to=20ensure=20multi-tenancy?= =?UTF-8?q?=20works=20and=20fixed=20several=20multi-tenancy=20issues=20in?= =?UTF-8?q?=20workflow.=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=2030698=20=20ALF-9541:=20Fixed=20HistoricTaskEntity=20update?= =?UTF-8?q?=20when=20TaskEntity=20is=20loaded=20from=20DB=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=2030699=20=20ALF-10084,=20?= =?UTF-8?q?ALF-10242.=20Fixed=20issues=20and=20added=20WorkflowService=20m?= =?UTF-8?q?ethods=20to=20get=20workflow=20instances=20without=20filtering?= =?UTF-8?q?=20by=20definition=20id.=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=2030750=20=20ALF-10197,=20Added=20the=20ability=20?= =?UTF-8?q?to=20auto-complete=20Start=20Tasks=20in=20Activiti.=20If=20a=20?= =?UTF-8?q?start=20task=20extends=20the=20bpm:activitiStartTask=20type=20o?= =?UTF-8?q?r=20implements=20the=20bpm:endAutomatically=20aspect=20then=20t?= =?UTF-8?q?he=20task=20will=20be=20ended=20as=20soon=20as=20the=20workflow?= =?UTF-8?q?=20instance=20is=20started.=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2030796=20=20ALF-10374=20Fixed=20failing=20Mult?= =?UTF-8?q?iTDemoTest=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030908:?= =?UTF-8?q?=20Add=20logging=20for=20failed=20email=20domain=20lookups:=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=2030922:=20Rolling=20ba?= =?UTF-8?q?ck=20.classpath=20changes=20to=20Data=20Model.=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=2030930:=20Basic=20version=20of=20s?= =?UTF-8?q?ite=20invite=20working=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=2030931:=20THOR-172:=20Switch=20Tenant=20via=20public=20API?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030936:=20Allow=20f?= =?UTF-8?q?or=20repo=20web=20scripts=20to=20switch=20to=20user's=20default?= =?UTF-8?q?=20tenant=20via=20-default-=20tenant=20id:=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=2030937:=20Implementation=20of=20THOR-214?= =?UTF-8?q?.=20There=20is=20now=20a=20new=20repo=20webscript=20to=20retrie?= =?UTF-8?q?ve=20signup=20status=20for=20a=20given=20{id,=20key}=20pair.=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=2030938:=20Allow=20dev?= =?UTF-8?q?=20email=20address=20to=20be=20specified=20in=20properties=20fi?= =?UTF-8?q?le:=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030945:=20THOR-2?= =?UTF-8?q?21:=20Add=20(EntityLookup)=20cache=20to=20AccountDAO=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=2030946:=20Build=20fix.=20Ren?= =?UTF-8?q?aming=20a=20test=20infrastructure=20class=20so=20that=20it=20do?= =?UTF-8?q?esn't=20get=20picked=20up=20by=20the=20ant=20test=20targets.=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=2030955:=20THOR-222.=20?= =?UTF-8?q?Added=20inviter=20first=20and=20last=20name=20to=20invitation-s?= =?UTF-8?q?tatus.get=20webscript.=20=20=20=20=20=20=20=20=2046843:=20Merge?= =?UTF-8?q?d=20from=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=2035694:=20Merged=20BRANCHES/?= =?UTF-8?q?DEV/THOR1=20to=20BRANCHES/DEV/CLOUD1:=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2030997:=20Firefox=20scrollbars=20removed=20?= =?UTF-8?q?on=20"invitation"=20and=20"signup"=20pages=20(now=20using=20new?= =?UTF-8?q?=20helper=20method=20Alfresco.util.createYUIOverlay)=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=2031001:=20Impl=20of=20THOR-2?= =?UTF-8?q?23.=20Webscripts=20for=20getting=20pending=20invitations.=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=2031002:=20Invite=20-=20re?= =?UTF-8?q?direct=20bug=20fixed,=20removed=20old=20code=20matching=20previ?= =?UTF-8?q?ous=20webscript=20api,=20email=20picker=20style=20fixes=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=2031003:=20Addendum=20for?= =?UTF-8?q?=20THOR-223.=20I've=20added=20an=20explicit=20test=20to=20recor?= =?UTF-8?q?d=20the=20fact=20that=20pending-invitations.get=20to=20a=20non-?= =?UTF-8?q?existent=20site=20returns=20200=20and=20an=20empty=20collection?= =?UTF-8?q?=20rather=20than=20a=20404.=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=2031004:=20Adding=20REST-client=20.rcq=20files=20as=20pa?= =?UTF-8?q?rt=20of=20THOR-223=20=20=20=20=20=20=20=20=2046848:=20Merged=20?= =?UTF-8?q?from=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=2035700:=20Merged=20BRANCHES/DEV?= =?UTF-8?q?/THOR1=20to=20BRANCHES/DEV/CLOUD1:=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2031014:=20(RECORD=20ONLY)=20Merged=20HEAD=20to?= =?UTF-8?q?=20BRANCHES/DEV/THOR1:=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2030999:=20ALF-9957=20-=20MT:=20test=20and?= =?UTF-8?q?=20fix=20subscriptions=20(followers)=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2031020:=20Update=20invite=20email=20template?= =?UTF-8?q?=20to=20bring=20in=20line=20with=20wireframe=20and=20text=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=2031021:=20Apply=20latest?= =?UTF-8?q?=20sanitized=20email=20blacklist:=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=2031030:=20Fixed=20THOR-226=20"DocLib=20"Detailed?= =?UTF-8?q?=20View"=20(default)=20does=20not=20list=20items=20-=20note:=20?= =?UTF-8?q?"Simple=20View"=20seems=20to=20be=20OK"=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2031033:=20THOR-228:=20Update=20aws=20sample?= =?UTF-8?q?=20file=20with=20quota=20config=20for=20cachingcontentstore=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=2031036:=20Fixed=20THOR?= =?UTF-8?q?-236=20"Webscript=20URL=20clash=20in=20signup"=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=2031037:=20THOR-175:=20set=20and=20?= =?UTF-8?q?enforce=20per-tenant=20quota=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2031043:=20Fixed=20THOR-174=20"F27:=20User=20can=20sw?= =?UTF-8?q?itch=20between=20networks=20they=20belong=20to"=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2046854:=20Merged=20from=20BRANCHES/DEV/CLOUD2=20to?= =?UTF-8?q?=20BRANCHES/DEV/CONV=5FV413=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?35725:=20Merged=20BRANCHES/DEV/THOR1=20to=20BRANCHES/DEV/CLOUD1?= =?UTF-8?q?:=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2031124:=20Fix=20fo?= =?UTF-8?q?r=20THOR-145.=20This=20check-in=20makes=20the=20Cloud=20Signup?= =?UTF-8?q?=20and=20Invitation=20workflows=20hidden=20within=20Share=20-?= =?UTF-8?q?=20users=20can't=20initiate=20them=20via=20"Start=20workflow...?= =?UTF-8?q?"=20=20=20=2048016:=20Merged=20BRANCHES/DEV/CONV=5FV413=20to=20?= =?UTF-8?q?BRANCHES/DEV/CONV=5FHEAD:=20=20=20=20=20=20=20=20=2046793:=20Me?= =?UTF-8?q?rged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20?= =?UTF-8?q?=20=20=20=20=20=20=20=2046795:=20Merged=20BRANCHES/DEV/CLOUD2?= =?UTF-8?q?=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20=20467?= =?UTF-8?q?96:=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV?= =?UTF-8?q?413:=20=20=20=2048030:=20Merged=20BRANCHES/DEV/CONV=5FV413=20to?= =?UTF-8?q?=20BRANCHES/DEV/CONV=5FHEAD:=20=20=20=20=20=20=20=20=2046820:?= =?UTF-8?q?=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413?= =?UTF-8?q?:=20=20=20=20=20=20=20=20=20=20=20=20=20=2035657:=20Merged=20BR?= =?UTF-8?q?ANCHES/DEV/THOR1=20to=20BRANCHES/DEV/CLOUD1:=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=2030556:=20THOR-135F103:?= =?UTF-8?q?=20Is=20e-mail=20address=20accepted=20by=20Alfresco=3F=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030562:=20Fixing?= =?UTF-8?q?=20a=20typo=20in=20the=20email-validation=20FTL.=20It=20was=20r?= =?UTF-8?q?eturning=20invalid=20JSON=20-=20no=20opening=20"=20on=20a=20str?= =?UTF-8?q?ing.=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030?= =?UTF-8?q?569:=20THOR-156:=20switch=20to=20secondary=20tenant=20(initiall?= =?UTF-8?q?y=20via=20@@login)=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2030571:=20THOR-99:=20Thor=20build=20=20=20=2048037:?= =?UTF-8?q?=20Merged=20BRANCHES/DEV/CONV=5FV413=20to=20BRANCHES/DEV/CONV?= =?UTF-8?q?=5FHEAD:=20=20=20=20=20=20=20=20=2046821:=20Merged=20BRANCHES/D?= =?UTF-8?q?EV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2035659:=20Merged=20BRANCHES/DEV/THOR1=20to?= =?UTF-8?q?=20BRANCHES/DEV/CLOUD1:=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2030586:=20THOR-166.=20I've=20added=20an=20addi?= =?UTF-8?q?tional=20check=20at=20the=20start=20of=20the=20signup=20workflo?= =?UTF-8?q?w=20that=20checks=20if=20the=20email=20is=20blocked.=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030587:=20THOR-1?= =?UTF-8?q?63:=20S3ContentReader=20fails=20to=20getObjectDetails=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030592:=20THOR-1?= =?UTF-8?q?56:=20switch=20to=20secondary=20tenant=20(initially=20via=20@@l?= =?UTF-8?q?ogin)=20=20=20=20=20=20=20=20=20=20=20=20=20=2035660:=20Merged?= =?UTF-8?q?=20BRANCHES/DEV/THOR1=20to=20BRANCHES/DEV/CLOUD1:=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030607:=20(RECORD?= =?UTF-8?q?=20ONLY)=20Merged=20HEAD=20to=20BRANCHES/DEV/THOR1:=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20302?= =?UTF-8?q?08:=20Remaining=20commits=20for=20ALF-9510=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030218:=20Fix?= =?UTF-8?q?=20build=20-=20add=20missing=20files=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030254:=20Encryptio?= =?UTF-8?q?n=20related=20documentation,=20source=20code=20comments=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=2030392:=20Fix=20for=20ALF-10205=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=2030405:=20Fix=20for=20?= =?UTF-8?q?ALF-10189=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2030406:=20Fix=20for=20ALF-10189:=20part=202=20?= =?UTF-8?q?-=20minor=20update=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2030613:=20THOR-148.=20The=20cloud=20test=20target=20?= =?UTF-8?q?was=20accidentally=20excluding=20*RestTest.java.=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=2030613:=20THOR-148.=20?= =?UTF-8?q?The=20cloud=20test=20target=20was=20accidentally=20excluding=20?= =?UTF-8?q?*RestTest.java.=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=2030614:=20Revert=20some=20of=20the=20additional=20email?= =?UTF-8?q?=20checks=20in=20registration=20process=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=2030615:=20Set=20ignore=20patter?= =?UTF-8?q?ns=20for=20build=20dir=20in=20thor=20module=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=2030619:=20Merged=20HEAD?= =?UTF-8?q?=20to=20BRANCHES/DEV/THOR1:=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=2030618:=20Additional=20test?= =?UTF-8?q?=20classes=20that=20allow=20for=20easier=20testing=20of=20Notif?= =?UTF-8?q?ications=20(emails=20mostly).=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=2030622:=20Ensure=20use=20of=20System=20u?= =?UTF-8?q?ser,=20not=20system=20user=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2030624:=20Removed=20deep=20merge=20info=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030625:=20?= =?UTF-8?q?Switch=20off=20creation=20of=20missing=20people,=20use=20Admin?= =?UTF-8?q?=20instead=20of=20System=20=20=20=20=20=20=20=20=2046824:=20Mer?= =?UTF-8?q?ged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV413:=20?= =?UTF-8?q?=20=20=20=20=20=20=20=2046828:=20Merged=20BRANCHES/DEV/CLOUD2?= =?UTF-8?q?=20to=20BRANCHES/DEV/CONV=5FV413:=20=20=20=2048038:=20Merged=20?= =?UTF-8?q?DEV/CONV=5FV413=20to=20DEV/CONV=5FHEAD=20(ui-only)=20=20=20=20?= =?UTF-8?q?=20=20=20=20=2046830:=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRA?= =?UTF-8?q?NCHES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20=20=20=20=20Merge?= =?UTF-8?q?d=20BRANCHES/DEV/THOR1=20to=20BRANCHES/DEV/CLOUD1:=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=2030737:=20(RECORD=20ONLY)=20Merged=20HEA?= =?UTF-8?q?D=20to=20BRANCHES/DEV/THOR1:=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=2030736:=20ALF-6706=20-=20MT:=20activities=20no?= =?UTF-8?q?t=20generated=20(for=20tenants)=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=2030738:=20Site=20invite=20(rough=20version,=20not=20finish?= =?UTF-8?q?ed)=20=20=20=20=20=20=20=20=20=20=20=2030741:=20THOR-175:=20Set?= =?UTF-8?q?=20and=20enforce=20file=20space=20quota=20for=20tenant=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=2030752:=20Site=20invite=20-=20added?= =?UTF-8?q?=20som=20padding=20to=20user=20suggestion=20list=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2030753:=20Disabling=202=20tests=20while=20I?= =?UTF-8?q?=20fix=20them.=20=20=20=20=20=20=20=20=20=20=20=2030758:=20THOR?= =?UTF-8?q?-172=20F63:=20Switch=20Tenant=20via=20public=20REST=20API:=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=2030764:=20Tweak=20to=20Activiti?= =?UTF-8?q?=20integration=20code=20to=20prevent=20it=20from=20trying=20to?= =?UTF-8?q?=20create=20person=20nodes=20for=20the=20System=20user.=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=2030766:=20Implementation=20of=20TH?= =?UTF-8?q?OR-196.=20Inviting=20multiple=20email=20addresses=20in=20a=20si?= =?UTF-8?q?ngle=20call.=20=20=20=20=20=20=20=20=20=20=20=2030769:=20Re-ena?= =?UTF-8?q?ble=20MultiTDemoTest=20=20=20=20=20=20=20=20=20=20=20=2030775:?= =?UTF-8?q?=20Site=20invite=20=20=20=20=20=20=20=20=20=20=20=2030776:=20TH?= =?UTF-8?q?OR-172:=20Switch=20Tenant=20via=20public=20API=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2030785:=20Add=20tenant=20id=20to=20account?= =?UTF-8?q?=20info=20returned=20in=20Thor=20responses=20=20=20=2048043:=20?= =?UTF-8?q?Merged=20DEV/CONV=5FV413=20to=20DEV/CONV=5FHEAD=20=20=20=20=20?= =?UTF-8?q?=20=20=20=2046831:=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCH?= =?UTF-8?q?ES/DEV/CONV=5FV413:=20=20=20=20=20=20=20=20=20=20=20=20Merged?= =?UTF-8?q?=20BRANCHES/DEV/THOR1=20to=20BRANCHES/DEV/CLOUD1:=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=2030813:=20Add=20Eclipse=20proje?= =?UTF-8?q?ct=20for=20Thor-Share=20module:=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=2030815:=20THOR-175:=20Set=20and=20enforce=20file?= =?UTF-8?q?=20space=20quota=20for=20tenant=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=2030817:=20Switch=20network=20skeleton=20code=20an?= =?UTF-8?q?d=20minor=20fixes=20=20=20=20=20=20=20=20=20=20=20=20=20=20=203?= =?UTF-8?q?0818:=20Update=20Share=20Node=20Browser=20(at=20least=20for=20T?= =?UTF-8?q?HOR)=20to=20allow=20option=20to=20retrieve=20"storeroot"=20via?= =?UTF-8?q?=20DB=20query=20=20=20=20=20=20=20=20=20=20=20=20=20=20=2030826?= =?UTF-8?q?:=20Add=20distribute-solr=20to=20Thor=20builds=20=20=20=2048045?= =?UTF-8?q?:=20Merged=20BRANCHES/DEV/CLOUD2=20to=20BRANCHES/DEV/CONV=5FV41?= =?UTF-8?q?3:=20=20=20=20=20=20=20=20Merged=20BRANCHES/DEV/THOR1=20to=20BR?= =?UTF-8?q?ANCHES/DEV/CLOUD1:=20=20=20=20=20=20=20=20=20=20=20=2030737:=20?= =?UTF-8?q?(RECORD=20ONLY)=20Merged=20HEAD=20to=20BRANCHES/DEV/THOR1:=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=2030736:=20ALF-6706=20-=20MT:=20?= =?UTF-8?q?activities=20not=20generated=20(for=20tenants)=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=2030738:=20Site=20invite=20(rough=20version,?= =?UTF-8?q?=20not=20finished)=20=20=20=20=20=20=20=20=20=20=20=2030741:=20?= =?UTF-8?q?THOR-175:=20Set=20and=20enforce=20file=20space=20quota=20for=20?= =?UTF-8?q?tenant=20=20=20=20=20=20=20=20=20=20=20=2030752:=20Site=20invit?= =?UTF-8?q?e=20-=20added=20som=20padding=20to=20user=20suggestion=20list?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=2030753:=20Disabling=202=20te?= =?UTF-8?q?sts=20while=20I=20fix=20them.=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=2030758:=20THOR-172=20F63:=20Switch=20Tenant=20via=20public=20?= =?UTF-8?q?REST=20API:=20=20=20=20=20=20=20=20=20=20=20=2030764:=20Tweak?= =?UTF-8?q?=20to=20Activiti=20integration=20code=20to=20prevent=20it=20fro?= =?UTF-8?q?m=20trying=20to=20create=20person=20nodes=20for=20the=20System?= =?UTF-8?q?=20user.=20=20=20=20=20=20=20=20=20=20=20=2030766:=20Implementa?= =?UTF-8?q?tion=20of=20THOR-196.=20Inviting=20multiple=20email=20addresses?= =?UTF-8?q?=20in=20a=20single=20call.=20=20=20=20=20=20=20=20=20=20=20=203?= =?UTF-8?q?0769:=20Re-enable=20MultiTDemoTest=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=2030775:=20Site=20invite=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=2030776:=20THOR-172:=20Switch=20Tenant=20via=20public=20API?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=2030785:=20Add=20tenant=20id?= =?UTF-8?q?=20to=20account=20info=20returned=20in=20Thor=20responses?= 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@48251 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../authentication-services-context.xml | 3 +- .../alfresco/authority-services-context.xml | 3 + config/alfresco/bootstrap-context.xml | 2 +- .../bootstrap/categoriesEmptyRoot.xml | 15 + .../bootstrap/webScriptsNoSamples.xml | 34 + config/alfresco/cache-context.xml | 15 +- config/alfresco/core-services-context.xml | 8 +- config/alfresco/import-export-context.xml | 6 - config/alfresco/mt/mt-base-context.xml | 8 + config/alfresco/repository.properties | 8 + config/alfresco/site-services-context.xml | 7 +- config/alfresco/tx-cache-context.xml | 17 +- .../CMISAbstractDictionaryService.java | 11 +- .../CMISStrictDictionaryService.java | 13 +- .../repo/activities/feed/FeedNotifierJob.java | 9 +- .../activities/feed/FeedNotifierTest.java | 5 +- .../repo/admin/ConfigurationChecker.java | 8 +- .../repo/admin/patch/AbstractPatch.java | 29 +- .../patch/impl/AuthorityMigrationPatch.java | 8 +- .../repo/domain/schema/SchemaBootstrap.java | 31 +- .../domain/tenant/TenantAdminDAOTest.java | 24 +- .../repo/i18n/MessageServiceImpl.java | 10 +- .../repo/importer/ImporterComponent.java | 27 +- .../invitation/InvitationServiceImpl.java | 9 +- .../org/alfresco/repo/model/Repository.java | 105 +-- .../repo/module/AbstractModuleComponent.java | 9 +- .../alfresco/repo/module/ModuleComponent.java | 8 +- .../repo/module/ModuleComponentHelper.java | 75 +- .../module/ModuleComponentHelperTest.java | 9 +- .../repo/module/ModuleServiceImpl.java | 12 +- .../impl/lucene/ADMLuceneIndexerImpl.java | 8 +- .../AbstractAuthenticationComponent.java | 12 +- .../AuthenticationComponentImpl.java | 9 +- .../security/authority/AuthorityDAOImpl.java | 21 +- .../HomeFolderProviderSynchronizer.java | 55 +- .../HomeFolderProviderSynchronizerTest.java | 23 +- .../security/person/PersonServiceImpl.java | 79 +- .../alfresco/repo/site/SiteServiceImpl.java | 70 +- .../repo/tenant/MultiTAdminServiceImpl.java | 445 +++++---- .../alfresco/repo/tenant/MultiTDemoTest.java | 892 +++++++++--------- .../tenant/MultiTNodeServiceInterceptor.java | 8 +- .../repo/tenant/MultiTServiceImpl.java | 2 +- .../repo/tenant/RunAsTenantInterceptor.java | 78 ++ .../repo/tenant/SingleTAdminServiceImpl.java | 9 +- .../repo/tenant/TenantAdminService.java | 9 +- .../repo/tenant/TenantBasicDataSource.java | 49 + .../repo/tenant/TenantInterpreter.java | 55 +- .../tenant/TenantRoutingContentStore.java | 3 +- .../repo/tenant/TenantRoutingDataSource.java | 134 +++ .../usage/UserUsageTrackingComponent.java | 36 +- .../repo/version/MigrationCleanupJob.java | 10 +- .../repo/workflow/WorkflowServiceImpl.java | 4 +- .../activiti/script/ActivitiScriptBase.java | 1 + .../service/cmr/module/ModuleService.java | 4 +- .../service/cmr/security/PersonService.java | 12 +- 55 files changed, 1629 insertions(+), 927 deletions(-) create mode 100644 config/alfresco/bootstrap/categoriesEmptyRoot.xml create mode 100644 config/alfresco/bootstrap/webScriptsNoSamples.xml create mode 100644 source/java/org/alfresco/repo/tenant/RunAsTenantInterceptor.java create mode 100644 source/java/org/alfresco/repo/tenant/TenantBasicDataSource.java create mode 100644 source/java/org/alfresco/repo/tenant/TenantRoutingDataSource.java diff --git a/config/alfresco/authentication-services-context.xml b/config/alfresco/authentication-services-context.xml index b6e104e713..9a2dd30f0b 100644 --- a/config/alfresco/authentication-services-context.xml +++ b/config/alfresco/authentication-services-context.xml @@ -278,6 +278,7 @@ + @@ -314,7 +315,7 @@ - ${server.transaction.allow-writes} + ${create.missing.people} diff --git a/config/alfresco/authority-services-context.xml b/config/alfresco/authority-services-context.xml index 23ccb5d757..bc0c6e4a60 100644 --- a/config/alfresco/authority-services-context.xml +++ b/config/alfresco/authority-services-context.xml @@ -88,6 +88,9 @@ + + + diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index daf493795f..a58806787c 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -793,10 +793,10 @@ - ${spaces.store} /${spaces.company_home.childname} + diff --git a/config/alfresco/bootstrap/categoriesEmptyRoot.xml b/config/alfresco/bootstrap/categoriesEmptyRoot.xml new file mode 100644 index 0000000000..b7bbabcf28 --- /dev/null +++ b/config/alfresco/bootstrap/categoriesEmptyRoot.xml @@ -0,0 +1,15 @@ + + + + categories + + + General + + + + + + \ No newline at end of file diff --git a/config/alfresco/bootstrap/webScriptsNoSamples.xml b/config/alfresco/bootstrap/webScriptsNoSamples.xml new file mode 100644 index 0000000000..f9550b2d75 --- /dev/null +++ b/config/alfresco/bootstrap/webScriptsNoSamples.xml @@ -0,0 +1,34 @@ + + + + + + + + ${webscripts.url_addressable_web_services} + space-icon-default + ${webscripts.url_addressable_web_services} + ${webscripts.web_scripts} + + + + + + + + + + + true + ${webscripts.what_are_web_scripts} + contentUrl=classpath:alfresco/bootstrap/webscripts/readme.html|mimetype=text/html|size=|encoding=UTF-8|locale=en_US_ + + + readme.html + + + + + + + \ No newline at end of file diff --git a/config/alfresco/cache-context.xml b/config/alfresco/cache-context.xml index d53fb654b4..25d03b122a 100644 --- a/config/alfresco/cache-context.xml +++ b/config/alfresco/cache-context.xml @@ -432,7 +432,7 @@ - + @@ -457,8 +457,19 @@ - + + + + + + + + + + + + diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index de23c78b72..9c0da6bbcb 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -238,10 +238,12 @@ - - + + + + - + ${db.driver} diff --git a/config/alfresco/import-export-context.xml b/config/alfresco/import-export-context.xml index ca88cd2fdd..9aa2f29eb8 100644 --- a/config/alfresco/import-export-context.xml +++ b/config/alfresco/import-export-context.xml @@ -40,9 +40,6 @@ - - - @@ -93,9 +90,6 @@ - - - diff --git a/config/alfresco/mt/mt-base-context.xml b/config/alfresco/mt/mt-base-context.xml index 217a420718..562a9fb310 100644 --- a/config/alfresco/mt/mt-base-context.xml +++ b/config/alfresco/mt/mt-base-context.xml @@ -33,6 +33,14 @@ + + + + diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index f721a960d3..5d1be6df72 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -513,6 +513,13 @@ system.usages.updateBatchSize=50 # Repository endpoint - used by Activity Service repo.remote.endpoint=/service +# Some authentication mechanisms may need to create people +# in the repository on demand. This enables that feature. +# If disabled an error will be generated for missing +# people. If enabled then a person will be created and +# persisted. +create.missing.people=${server.transaction.allow-writes} + # Create home folders as people are created (true) or create them lazily (false) home.folder.creation.eager=true @@ -979,6 +986,7 @@ cache.immutableSingletonSharedCache.maxItems=12000 cache.remoteAlfrescoTicketService.ticketsCache.maxItems=1000 cache.contentDiskDriver.fileInfoCache.maxItems=1000 cache.globalConfigSharedCache.maxItems=1000 +cache.siteNodeRefSharedCache.maxItems=5000 # # Download Service Limits, in bytes diff --git a/config/alfresco/site-services-context.xml b/config/alfresco/site-services-context.xml index 5409b4f5f1..c5d0751f00 100644 --- a/config/alfresco/site-services-context.xml +++ b/config/alfresco/site-services-context.xml @@ -137,18 +137,16 @@ - + - ./${spaces.company_home.childname}/st:sites - @@ -156,6 +154,9 @@ + + + diff --git a/config/alfresco/tx-cache-context.xml b/config/alfresco/tx-cache-context.xml index 0a75fedd9c..1b3e3bedf9 100644 --- a/config/alfresco/tx-cache-context.xml +++ b/config/alfresco/tx-cache-context.xml @@ -563,7 +563,7 @@ org.alfresco.tenantsTransactionalCache - + @@ -583,4 +583,19 @@ + + + + + + + + + org.alfresco.cache.siteNodeRefTransactionalCache + + + + + + diff --git a/source/java/org/alfresco/cmis/dictionary/CMISAbstractDictionaryService.java b/source/java/org/alfresco/cmis/dictionary/CMISAbstractDictionaryService.java index a17eeb7455..24bd8be62d 100644 --- a/source/java/org/alfresco/cmis/dictionary/CMISAbstractDictionaryService.java +++ b/source/java/org/alfresco/cmis/dictionary/CMISAbstractDictionaryService.java @@ -536,7 +536,16 @@ public abstract class CMISAbstractDictionaryService extends AbstractLifecycleBea protected void onBootstrap(ApplicationEvent event) { afterDictionaryInit(); - dictionaryDAO.register(this); + + // TODO revisit (for KS and/or 1.1) + if (dictionaryDAO != null) + { + dictionaryDAO.register(this); + } + else + { + logger.error("DictionaryDAO is null - hence CMIS Dictionary not registered for updates"); + } } /* diff --git a/source/java/org/alfresco/cmis/dictionary/CMISStrictDictionaryService.java b/source/java/org/alfresco/cmis/dictionary/CMISStrictDictionaryService.java index 5f3daa08b9..d59a39c60b 100644 --- a/source/java/org/alfresco/cmis/dictionary/CMISStrictDictionaryService.java +++ b/source/java/org/alfresco/cmis/dictionary/CMISStrictDictionaryService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -106,6 +106,17 @@ public class CMISStrictDictionaryService extends CMISAbstractDictionaryService { CMISTypeId typeId = cmisMapping.getCmisTypeId(CMISScope.RELATIONSHIP, CMISMapping.RELATIONSHIP_QNAME); ClassDefinition classDef = dictionaryService.getClass(cmisMapping.getCmisType(typeId.getQName())); + + // from Thor + if (classDef == null) + { + if (classQNames.size() != 0) + { + logger.warn("Unexpected - no class for "+cmisMapping.getCmisType(typeId.getQName())+" - cannot create assocDefs for: "+classQNames); + } + return; + } + CMISAbstractTypeDefinition objectTypeDef = new CMISRelationshipTypeDefinition(cmisMapping, typeId, dictionaryService, classDef, null); registry.registerTypeDefinition(objectTypeDef); diff --git a/source/java/org/alfresco/repo/activities/feed/FeedNotifierJob.java b/source/java/org/alfresco/repo/activities/feed/FeedNotifierJob.java index af79b56791..c3e2018c61 100644 --- a/source/java/org/alfresco/repo/activities/feed/FeedNotifierJob.java +++ b/source/java/org/alfresco/repo/activities/feed/FeedNotifierJob.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -24,6 +24,8 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.tenant.Tenant; import org.alfresco.repo.tenant.TenantAdminService; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; @@ -74,15 +76,14 @@ public class FeedNotifierJob implements Job List tenants = tenantAdminService.getAllTenants(); for (Tenant tenant : tenants) { - String tenantDomain = tenant.getTenantDomain(); - AuthenticationUtil.runAs(new RunAsWork() + TenantUtil.runAsSystemTenant(new TenantRunAsWork() { public Object doWork() throws Exception { feedNotifier.execute(repeatIntervalMins); return null; } - }, tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); + }, tenant.getTenantDomain()); } } } diff --git a/source/java/org/alfresco/repo/activities/feed/FeedNotifierTest.java b/source/java/org/alfresco/repo/activities/feed/FeedNotifierTest.java index 8740ad05a9..c8dc9a2a1b 100644 --- a/source/java/org/alfresco/repo/activities/feed/FeedNotifierTest.java +++ b/source/java/org/alfresco/repo/activities/feed/FeedNotifierTest.java @@ -238,9 +238,8 @@ public class FeedNotifierTest /** * ALF-16155 test */ - // TODO fix CLOUD-1348 - //@Test - public void xtestFailedNotifications() + @Test + public void testFailedNotifications() { AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); diff --git a/source/java/org/alfresco/repo/admin/ConfigurationChecker.java b/source/java/org/alfresco/repo/admin/ConfigurationChecker.java index 965c363ce6..ea3e5af168 100644 --- a/source/java/org/alfresco/repo/admin/ConfigurationChecker.java +++ b/source/java/org/alfresco/repo/admin/ConfigurationChecker.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -227,6 +227,12 @@ public class ConfigurationChecker extends AbstractLifecycleBean { String msg = I18NUtil.getMessage(ERR_MISSING_INDEXES, missingStoreIndexes); logger.error(msg); + + for (StoreRef missingIndexStoreRef : missingIndexStoreRefs) + { + logger.error("---> "+missingIndexStoreRef); + } + String msgRecover = I18NUtil.getMessage(MSG_HOWTO_INDEX_RECOVER); logger.info(msgRecover); } diff --git a/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java b/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java index ef47fed96b..1f5f4e8e36 100644 --- a/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -25,13 +25,13 @@ import java.util.Date; import java.util.List; import org.alfresco.error.AlfrescoRuntimeException; -import org.springframework.extensions.surf.util.I18NUtil; import org.alfresco.repo.node.integrity.IntegrityChecker; import org.alfresco.repo.security.authentication.AuthenticationContext; import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.tenant.Tenant; import org.alfresco.repo.tenant.TenantAdminService; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.admin.PatchException; @@ -43,6 +43,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.extensions.surf.util.I18NUtil; /** * Base implementation of the patch. This class ensures that the patch is thread- and transaction-safe. @@ -403,24 +404,24 @@ public abstract class AbstractPatch implements Patch, ApplicationEventPublisher { // downgrade integrity checking IntegrityChecker.setWarnInTransaction(); - + String report = applyInternal(); - if ((tenantAdminService != null) && tenantAdminService.isEnabled() && applyToTenants) + if ((tenantAdminService != null) && tenantAdminService.isEnabled() && applyToTenants) { - List tenants = tenantAdminService.getAllTenants(); + List tenants = tenantAdminService.getAllTenants(); for (Tenant tenant : tenants) - { - String tenantDomain = tenant.getTenantDomain(); - String tenantReport = AuthenticationUtil.runAs(new RunAsWork() + { + String tenantDomain = tenant.getTenantDomain(); + String tenantReport = TenantUtil.runAsSystemTenant(new TenantRunAsWork() { - public String doWork() throws Exception + public String doWork() throws Exception { - return applyInternal(); + return applyInternal(); } - }, tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); - - report = report + "\n" + tenantReport + " (for tenant: " + tenantDomain + ")"; + }, tenantDomain); + + report = report + "\n" + tenantReport + " (for tenant: " + tenantDomain + ")"; } return report; diff --git a/source/java/org/alfresco/repo/admin/patch/impl/AuthorityMigrationPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/AuthorityMigrationPatch.java index 9877b268c8..00fe3b57d4 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/AuthorityMigrationPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/AuthorityMigrationPatch.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -290,9 +290,9 @@ public class AuthorityMigrationPatch extends AbstractPatch // Disable rules ruleService.disableRules(); // Authentication - String systemUser = AuthenticationUtil.getSystemUserName(); - systemUser = tenantAdminService.getDomainUser(systemUser, tenantDomain); - AuthenticationUtil.setRunAsUser(systemUser); + // TODO tenant switch + String tenantSystemUser = tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain); + AuthenticationUtil.setRunAsUser(tenantSystemUser); } public void afterProcess() throws Throwable diff --git a/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java b/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java index 54b36b7018..5b8de39c0c 100644 --- a/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java +++ b/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java @@ -751,6 +751,9 @@ public class SchemaBootstrap extends AbstractLifecycleBean try { stmt.executeUpdate("drop table alf_bootstrap_lock"); + + // from Thor + executedStatementsThreadLocal.set(null); } catch (Throwable e) { @@ -791,6 +794,8 @@ public class SchemaBootstrap extends AbstractLifecycleBean if (create) { + long start = System.currentTimeMillis(); + // execute pre-create scripts (not patches) for (String scriptUrl : this.preCreateScriptUrls) { @@ -828,6 +833,11 @@ public class SchemaBootstrap extends AbstractLifecycleBean { executeScriptUrl(cfg, connection, scriptUrl); } + + if (logger.isInfoEnabled()) + { + logger.info("Create scripts executed in "+(System.currentTimeMillis()-start)+" ms"); + } } else { @@ -1478,12 +1488,16 @@ public class SchemaBootstrap extends AbstractLifecycleBean SchemaBootstrap.setMaxStringLength(maximumStringLength); } } - + @Override - protected synchronized void onBootstrap(ApplicationEvent event) + public synchronized void onBootstrap(ApplicationEvent event) { - // Use the application context to load resources - rpr = (ApplicationContext)event.getSource(); + // from Thor + if (event != null) + { + // Use the application context to load resources + rpr = (ApplicationContext)event.getSource(); + } // do everything in a transaction Session session = getSessionFactory().openSession(); @@ -1602,9 +1616,12 @@ public class SchemaBootstrap extends AbstractLifecycleBean // Reset the configuration cfg.setProperty(Environment.CONNECTION_PROVIDER, defaultConnectionProviderFactoryClass); - - // all done successfully - ((ApplicationContext) event.getSource()).publishEvent(new SchemaAvailableEvent(this)); + + if (event != null) + { + // all done successfully + ((ApplicationContext) event.getSource()).publishEvent(new SchemaAvailableEvent(this)); + } } catch (BootstrapStopException e) { diff --git a/source/java/org/alfresco/repo/domain/tenant/TenantAdminDAOTest.java b/source/java/org/alfresco/repo/domain/tenant/TenantAdminDAOTest.java index 44bc128ad0..eeb987f7e4 100644 --- a/source/java/org/alfresco/repo/domain/tenant/TenantAdminDAOTest.java +++ b/source/java/org/alfresco/repo/domain/tenant/TenantAdminDAOTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -33,7 +33,7 @@ import org.springframework.context.ApplicationContext; * @see TenantAdminDAO * * @author janv - * @since 4.0 (thor) + * @since 4.0 */ public class TenantAdminDAOTest extends TestCase { @@ -138,6 +138,10 @@ public class TenantAdminDAOTest extends TestCase TenantEntity tenantEntity= getTenant(tenantDomain); assertNull(tenantEntity); + TenantEntity newTenantEntity = new TenantEntity(); + newTenantEntity.setTenantDomain(tenantDomain); + newTenantEntity.setEnabled(false); + TenantEntity createTenantEntity = createTenant(tenantDomain, false); assertNotNull(createTenantEntity); @@ -153,6 +157,10 @@ public class TenantAdminDAOTest extends TestCase { final String tenantDomain = getName() + "-" + System.currentTimeMillis(); + final TenantEntity newTenantEntity = new TenantEntity(); + newTenantEntity.setTenantDomain(tenantDomain); + newTenantEntity.setEnabled(false); + RetryingTransactionCallback callback = new RetryingTransactionCallback() { public Void execute() throws Throwable @@ -192,6 +200,12 @@ public class TenantAdminDAOTest extends TestCase assertEquals(createTenantEntity, tenantUpdateEntity); assertFalse(tenantUpdateEntity.getEnabled()); + assertEquals(createTenantEntity.getTenantDomain(), tenantUpdateEntity.getTenantDomain()); + assertEquals(createTenantEntity.getEnabled(), tenantUpdateEntity.getEnabled()); + assertEquals(createTenantEntity.getTenantName(), tenantUpdateEntity.getTenantName()); + assertEquals(createTenantEntity.getContentRoot(), tenantUpdateEntity.getContentRoot()); + assertEquals(createTenantEntity.getDbUrl(), tenantUpdateEntity.getDbUrl()); + tenantUpdateEntity.setEnabled(true); updateTenant(tenantUpdateEntity); @@ -199,6 +213,12 @@ public class TenantAdminDAOTest extends TestCase assertNotNull(tenantEntity); assertTrue(tenantEntity.getEnabled()); + assertEquals(tenantEntity.getTenantDomain(), tenantUpdateEntity.getTenantDomain()); + assertEquals(tenantEntity.getEnabled(), tenantUpdateEntity.getEnabled()); + assertEquals(tenantEntity.getTenantName(), tenantUpdateEntity.getTenantName()); + assertEquals(tenantEntity.getContentRoot(), tenantUpdateEntity.getContentRoot()); + assertEquals(tenantEntity.getDbUrl(), tenantUpdateEntity.getDbUrl()); + deleteTenant(tenantDomain); assertNull(getTenant(tenantDomain)); diff --git a/source/java/org/alfresco/repo/i18n/MessageServiceImpl.java b/source/java/org/alfresco/repo/i18n/MessageServiceImpl.java index d5d6ef425e..f3020e14be 100644 --- a/source/java/org/alfresco/repo/i18n/MessageServiceImpl.java +++ b/source/java/org/alfresco/repo/i18n/MessageServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -45,6 +45,8 @@ import org.alfresco.repo.dictionary.RepositoryLocation; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; @@ -770,7 +772,7 @@ public class MessageServiceImpl implements MessageService logger.debug("Resetting messages ..."); } - AuthenticationUtil.runAs(new RunAsWork() + TenantUtil.runAsSystemTenant(new TenantRunAsWork() { public Object doWork() { @@ -783,8 +785,8 @@ public class MessageServiceImpl implements MessageService } return null; - } - }, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); + } + }, tenantDomain); if (logger.isDebugEnabled()) { diff --git a/source/java/org/alfresco/repo/importer/ImporterComponent.java b/source/java/org/alfresco/repo/importer/ImporterComponent.java index cf0458da95..4f1c3c859f 100644 --- a/source/java/org/alfresco/repo/importer/ImporterComponent.java +++ b/source/java/org/alfresco/repo/importer/ImporterComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -35,7 +35,7 @@ import org.alfresco.model.ContentModel; import org.alfresco.repo.importer.view.NodeContext; import org.alfresco.repo.model.filefolder.HiddenAspect; import org.alfresco.repo.policy.BehaviourFilter; -import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.usage.ContentUsageImpl; import org.alfresco.repo.version.Version2Model; import org.alfresco.repo.version.common.VersionUtil; @@ -87,8 +87,7 @@ import org.xml.sax.ContentHandler; * * @author David Caruana */ -public class ImporterComponent - implements ImporterService +public class ImporterComponent implements ImporterService { // Logger private static final Log logger = LogFactory.getLog(ImporterComponent.class); @@ -107,7 +106,6 @@ public class ImporterComponent private RuleService ruleService; private PermissionService permissionService; private AuthorityService authorityService; - private AuthenticationContext authenticationContext; private OwnableService ownableService; private VersionService versionService; private HiddenAspect hiddenAspect; @@ -205,14 +203,6 @@ public class ImporterComponent { this.authorityService = authorityService; } - - /** - * @param authenticationContext authenticationContext - */ - public void setAuthenticationContext(AuthenticationContext authenticationContext) - { - this.authenticationContext = authenticationContext; - } /** * @param ownableService ownableService @@ -1417,7 +1407,8 @@ public class ImporterComponent NodeRef nodeRef = assocRef.getChildRef(); // Note: non-admin authorities take ownership of new nodes - if (!(authenticationContext.isCurrentUserTheSystemUser() || authorityService.hasAdminAuthority())) + // from Thor + if (!(AuthenticationUtil.isRunAsUserTheSystemUser() || authorityService.hasAdminAuthority())) { ownableService.takeOwnership(nodeRef); } @@ -1425,7 +1416,9 @@ public class ImporterComponent // apply permissions List permissions = null; AccessStatus writePermission = permissionService.hasPermission(nodeRef, PermissionService.CHANGE_PERMISSIONS); - if (authenticationContext.isCurrentUserTheSystemUser() || writePermission.equals(AccessStatus.ALLOWED)) + + // from Thor + if (AuthenticationUtil.isRunAsUserTheSystemUser() || writePermission.equals(AccessStatus.ALLOWED)) { permissions = bindPermissions(node.getAccessControlEntries()); @@ -1603,7 +1596,9 @@ public class ImporterComponent // Apply permissions List permissions = null; AccessStatus writePermission = permissionService.hasPermission(existingNodeRef, PermissionService.CHANGE_PERMISSIONS); - if (authenticationContext.isCurrentUserTheSystemUser() || writePermission.equals(AccessStatus.ALLOWED)) + + // from Thor + if (AuthenticationUtil.isRunAsUserTheSystemUser() || writePermission.equals(AccessStatus.ALLOWED)) { boolean inheritPermissions = node.getInheritPermissions(); if (!inheritPermissions) diff --git a/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java b/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java index c4e3de0d62..da75ecd8a9 100644 --- a/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java +++ b/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -35,11 +35,11 @@ import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationException; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.authentication.PasswordGenerator; import org.alfresco.repo.security.authentication.UserNameGenerator; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.site.SiteModel; import org.alfresco.repo.transaction.TransactionalResourceHelper; import org.alfresco.repo.workflow.CancelWorkflowActionExecuter; @@ -1064,6 +1064,7 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli final String finalUserName = inviteeUserName; AuthenticationUtil.runAs(new RunAsWork() { + @SuppressWarnings("synthetic-access") public Object doWork() throws Exception { NodeRef person = personService.createPerson(properties); @@ -1265,6 +1266,7 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli String inviteePassword = created ? AuthenticationUtil.runAs(new RunAsWork() { + @SuppressWarnings("synthetic-access") public String doWork() { return createInviteeDisabledAccount(initeeUserNameFinal); @@ -1452,6 +1454,7 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli // Run as system user AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() { + @SuppressWarnings("synthetic-access") public Object doWork() throws Exception { QName type = nodeService.getType(siteRef); diff --git a/source/java/org/alfresco/repo/model/Repository.java b/source/java/org/alfresco/repo/model/Repository.java index 616b185b06..eeb0b6adee 100644 --- a/source/java/org/alfresco/repo/model/Repository.java +++ b/source/java/org/alfresco/repo/model/Repository.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -21,17 +21,13 @@ package org.alfresco.repo.model; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.avm.AVMNodeConverter; +import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.tenant.TenantAdminService; -import org.alfresco.repo.tenant.TenantDeployer; -import org.alfresco.repo.tenant.TenantUtil; -import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.avm.AVMNodeDescriptor; @@ -51,7 +47,6 @@ import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationListener; import org.springframework.extensions.surf.util.AbstractLifecycleBean; /** @@ -59,10 +54,10 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean; * * @author davidc */ -public class Repository implements ApplicationContextAware, ApplicationListener, TenantDeployer +public class Repository implements ApplicationContextAware { private ProcessorLifecycle lifecycle = new ProcessorLifecycle(); - + // dependencies private RetryingTransactionHelper retryingTransactionHelper; private NamespaceService namespaceService; @@ -71,13 +66,14 @@ public class Repository implements ApplicationContextAware, ApplicationListener, private FileFolderService fileFolderService; private PersonService personService; private AVMService avmService; - private TenantAdminService tenantAdminService; // company home - private StoreRef companyHomeStore; - private String companyHomePath; - final private Map companyHomeRefs = new ConcurrentHashMap(); - final private Map rootRefs = new ConcurrentHashMap(); + private StoreRef companyHomeStore; // ie. workspace://SpaceStore + private String companyHomePath; // ie. /app:company_home + + // note: cache is tenant-aware (if using EhCacheAdapter shared cache) + private SimpleCache singletonCache; // eg. for companyHomeNodeRef + private final String KEY_COMPANYHOME_NODEREF = "key.companyhome.noderef"; /** @@ -99,7 +95,12 @@ public class Repository implements ApplicationContextAware, ApplicationListener, { this.companyHomePath = companyHomePath; } - + + public void setSingletonCache(SimpleCache singletonCache) + { + this.singletonCache = singletonCache; + } + /** * Sets helper that provides transaction callbacks */ @@ -148,14 +149,6 @@ public class Repository implements ApplicationContextAware, ApplicationListener, this.personService = personService; } - /** - * Sets the tenant admin service - */ - public void setTenantAdminService(TenantAdminService tenantAdminService) - { - this.tenantAdminService = tenantAdminService; - } - /** * Sets the AVM service */ @@ -184,21 +177,19 @@ public class Repository implements ApplicationContextAware, ApplicationListener, { initContext(); } - + @Override protected void onShutdown(ApplicationEvent event) { } } - + /** * Initialise Repository Context */ protected void initContext() { - tenantAdminService.register(this); - - getCompanyHome(); + //NOOP } /** @@ -208,22 +199,10 @@ public class Repository implements ApplicationContextAware, ApplicationListener, */ public NodeRef getRootHome() { - String tenantDomain = tenantAdminService.getCurrentUserDomain(); - NodeRef rootRef = rootRefs.get(tenantDomain); - if (rootRef == null) - { - rootRef = TenantUtil.runAsSystemTenant(new TenantRunAsWork() - { - public NodeRef doWork() throws Exception - { - return nodeService.getRootNode(companyHomeStore); - } - }, tenantDomain); - rootRefs.put(tenantDomain, rootRef); - } - return rootRef; + // note: store root nodes are cached by the NodeDAO + return nodeService.getRootNode(companyHomeStore); } - + /** * Gets the Company Home * @@ -231,11 +210,10 @@ public class Repository implements ApplicationContextAware, ApplicationListener, */ public NodeRef getCompanyHome() { - String tenantDomain = tenantAdminService.getCurrentUserDomain(); - NodeRef companyHomeRef = companyHomeRefs.get(tenantDomain); + NodeRef companyHomeRef = singletonCache.get(KEY_COMPANYHOME_NODEREF); if (companyHomeRef == null) { - companyHomeRef = TenantUtil.runAsSystemTenant(new TenantRunAsWork() + companyHomeRef = AuthenticationUtil.runAs(new RunAsWork() { public NodeRef doWork() throws Exception { @@ -247,13 +225,14 @@ public class Repository implements ApplicationContextAware, ApplicationListener, if (refs.size() != 1) { throw new IllegalStateException("Invalid company home path: " + companyHomePath + " - found: " + refs.size()); + } + return refs.get(0); } - return refs.get(0); - } - }, true); - } - }, tenantDomain); - companyHomeRefs.put(tenantDomain, companyHomeRef); + }, true); + } + }, AuthenticationUtil.getSystemUserName()); + + singletonCache.put(KEY_COMPANYHOME_NODEREF, companyHomeRef); } return companyHomeRef; } @@ -460,25 +439,5 @@ public class Repository implements ApplicationContextAware, ApplicationListener, } return nodeRef; - } - - public void onEnableTenant() - { - init(); - } - - public void onDisableTenant() - { - destroy(); - } - - public void init() - { - initContext(); - } - - public void destroy() - { - companyHomeRefs.remove(tenantAdminService.getCurrentUserDomain()); } } diff --git a/source/java/org/alfresco/repo/module/AbstractModuleComponent.java b/source/java/org/alfresco/repo/module/AbstractModuleComponent.java index acdda771be..b6a40cb038 100644 --- a/source/java/org/alfresco/repo/module/AbstractModuleComponent.java +++ b/source/java/org/alfresco/repo/module/AbstractModuleComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -362,4 +362,11 @@ public abstract class AbstractModuleComponent implements ModuleComponent, BeanNa executed.put(tenantDomain, true); } } + + // from Thor + public final synchronized void shutdown() + { + String tenantDomain = tenantAdminService.getCurrentUserDomain(); + executed.put(tenantDomain, false); + } } diff --git a/source/java/org/alfresco/repo/module/ModuleComponent.java b/source/java/org/alfresco/repo/module/ModuleComponent.java index e4cc7b41de..058def40cf 100644 --- a/source/java/org/alfresco/repo/module/ModuleComponent.java +++ b/source/java/org/alfresco/repo/module/ModuleComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -92,4 +92,10 @@ public interface ModuleComponent * the associated module infrastructure. */ void execute(); + + /** + * Perform any cleanup required to remove module. + */ + // from Thor + void shutdown(); } diff --git a/source/java/org/alfresco/repo/module/ModuleComponentHelper.java b/source/java/org/alfresco/repo/module/ModuleComponentHelper.java index f662109450..df19604f41 100644 --- a/source/java/org/alfresco/repo/module/ModuleComponentHelper.java +++ b/source/java/org/alfresco/repo/module/ModuleComponentHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -36,6 +36,8 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.tenant.Tenant; import org.alfresco.repo.tenant.TenantAdminService; import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.module.ModuleDependency; @@ -242,15 +244,15 @@ public class ModuleComponentHelper { for (Tenant tenant : tenants) { - final String tenantDomain = tenant.getTenantDomain(); - AuthenticationUtil.runAs(new RunAsWork() + final String tenantDomain = tenant.getTenantDomain(); + TenantUtil.runAsSystemTenant(new TenantRunAsWork() { - public Object doWork() throws Exception + public Object doWork() throws Exception { - startModule(module, mapStartedModules.get(tenantDomain), mapExecutedComponents.get(tenantDomain)); - return null; + startModule(module, mapStartedModules.get(tenantDomain), mapExecutedComponents.get(tenantDomain)); + return null; } - }, tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); + }, tenantDomain); } } @@ -267,14 +269,14 @@ public class ModuleComponentHelper { for (Tenant tenant : tenants) { - AuthenticationUtil.runAs(new RunAsWork() + TenantUtil.runAsSystemTenant(new TenantRunAsWork() { - public Object doWork() throws Exception + public Object doWork() throws Exception { - checkForMissingModules(); - return null; + checkForMissingModules(); + return null; } - }, tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenant.getTenantDomain())); + }, tenant.getTenantDomain()); } } @@ -285,15 +287,15 @@ public class ModuleComponentHelper { for (Tenant tenant : tenants) { - final String tenantDomain = tenant.getTenantDomain(); - AuthenticationUtil.runAs(new RunAsWork() + final String tenantDomain = tenant.getTenantDomain(); + TenantUtil.runAsSystemTenant(new TenantRunAsWork() { - public Object doWork() throws Exception + public Object doWork() throws Exception { - checkForOrphanComponents(mapExecutedComponents.get(tenantDomain)); - return null; + checkForOrphanComponents(mapExecutedComponents.get(tenantDomain)); + return null; } - }, tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); + }, tenantDomain); } } } @@ -306,7 +308,42 @@ public class ModuleComponentHelper } }, AuthenticationUtil.getSystemUserName()); } - + + /** + * {@inheritDoc} + */ + public synchronized void shutdownModules() + { + // Check properties + PropertyCheck.mandatory(this, "serviceRegistry", serviceRegistry); + PropertyCheck.mandatory(this, "registryService", registryService); + PropertyCheck.mandatory(this, "moduleService", moduleService); + PropertyCheck.mandatory(this, "tenantAdminService", tenantAdminService); + + /* + * Ensure correct authentication + */ + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() throws Exception + { + // Get all the modules + List modules = moduleService.getAllModules(); + loggerService.info(I18NUtil.getMessage(MSG_FOUND_MODULES, modules.size())); + + for (ModuleDetails module : modules) + { + Map components = getComponents(module.getId()); + for (ModuleComponent component : components.values()) + { + component.shutdown(); + } + } + return null; + } + }, AuthenticationUtil.getSystemUserName()); + } + /** * Checks that all components have been executed or considered for execution. * @param executedComponents diff --git a/source/java/org/alfresco/repo/module/ModuleComponentHelperTest.java b/source/java/org/alfresco/repo/module/ModuleComponentHelperTest.java index 82c6d32325..cb3f342469 100644 --- a/source/java/org/alfresco/repo/module/ModuleComponentHelperTest.java +++ b/source/java/org/alfresco/repo/module/ModuleComponentHelperTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -247,7 +247,7 @@ public class ModuleComponentHelperTest extends BaseAlfrescoTestCase { throw new UnsupportedOperationException(); } - + @Override public List getMissingModules() { @@ -265,6 +265,11 @@ public class ModuleComponentHelperTest extends BaseAlfrescoTestCase // Done return details; } + + public void shutdownModules() + { + throw new UnsupportedOperationException(); + } } /** Keep track of the execution count */ diff --git a/source/java/org/alfresco/repo/module/ModuleServiceImpl.java b/source/java/org/alfresco/repo/module/ModuleServiceImpl.java index 390816a023..7bfaae30fd 100644 --- a/source/java/org/alfresco/repo/module/ModuleServiceImpl.java +++ b/source/java/org/alfresco/repo/module/ModuleServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -140,6 +140,16 @@ public class ModuleServiceImpl implements ApplicationContextAware, ModuleService { moduleComponentHelper.startModules(); } + + /** + * {@inheritDoc} + * + * @see ModuleComponentHelper#shutdownModules() + */ + public void shutdownModules() + { + moduleComponentHelper.shutdownModules(); + } /** * {@inheritDoc} 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 0d71a759f0..65b43b8d2c 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -58,6 +58,8 @@ import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.dictionary.AspectDefinition; @@ -710,14 +712,14 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp { // ETHREEOH-2014 - dictionary access should be in context of tenant (eg. full reindex with MT dynamic // models) - return AuthenticationUtil.runAs(new RunAsWork>() + return TenantUtil.runAsSystemTenant(new TenantRunAsWork>() { public List doWork() { return createDocumentsImpl(stringNodeRef, ftsStatus, indexAllProperties, includeDirectoryDocuments, cascade, pathsToRegenerate, childAssociationsSinceFlush, deltaReader, mainReader); } - }, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantService.getDomain(new NodeRef(stringNodeRef).getStoreRef().getIdentifier()))); + }, tenantService.getDomain(new NodeRef(stringNodeRef).getStoreRef().getIdentifier())); } else { diff --git a/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java b/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java index a312cd0dd0..2aa54dbfb7 100644 --- a/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java +++ b/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java @@ -31,7 +31,6 @@ import net.sf.acegisecurity.UserDetails; import net.sf.acegisecurity.providers.dao.User; import org.alfresco.model.ContentModel; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.sync.UserRegistrySynchronizer; import org.alfresco.repo.tenant.TenantContextHolder; import org.alfresco.repo.tenant.TenantService; @@ -249,12 +248,12 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC { throw new AuthenticationException("Null user name"); } - + if (isSystemUserName(userName)) { return setSystemUserAsCurrentUser(getUserDomain(userName)); } - + try { UserDetails ud = null; @@ -420,10 +419,9 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC return setCurrentUser(getGuestUserName(tenantDomain)); } else -{ + { throw new AuthenticationException("Guest authentication is not allowed"); } - } } @@ -489,7 +487,7 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC final String tenantDomain = userTenant.getSecond(); Authentication authentication = setCurrentUserImpl(userName); - AuthenticationUtil.runAs(new RunAsWork() + TenantUtil.runAsSystemTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -645,7 +643,7 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC { return authenticationContext.getSystemUserName(tenantDomain); } - + public String getUserDomain(String userName) { return authenticationContext.getUserDomain(userName); diff --git a/source/java/org/alfresco/repo/security/authentication/AuthenticationComponentImpl.java b/source/java/org/alfresco/repo/security/authentication/AuthenticationComponentImpl.java index ab4604378f..db73f00f21 100644 --- a/source/java/org/alfresco/repo/security/authentication/AuthenticationComponentImpl.java +++ b/source/java/org/alfresco/repo/security/authentication/AuthenticationComponentImpl.java @@ -30,8 +30,9 @@ import net.sf.acegisecurity.context.ContextHolder; import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken; import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.authentication.ntlm.NLTMAuthenticator; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; import org.alfresco.repo.tenant.TenantContextHolder; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.util.Pair; @@ -84,7 +85,7 @@ public class AuthenticationComponentImpl extends AbstractAuthenticationComponent { public String execute() throws Throwable { - return AuthenticationUtil.runAs(new RunAsWork() + return TenantUtil.runAsSystemTenant(new TenantRunAsWork() { public String doWork() throws Exception { @@ -94,7 +95,7 @@ public class AuthenticationComponentImpl extends AbstractAuthenticationComponent authenticationManager.authenticate(authentication); return normalized; } - }, getSystemUserName(getUserDomain(userName))); + }, tenantDomain); } }, true); @@ -104,7 +105,7 @@ public class AuthenticationComponentImpl extends AbstractAuthenticationComponent } else { - setCurrentUser(normalized, UserNameValidationMode.NONE); + setCurrentUser(normalized, UserNameValidationMode.NONE); } TenantContextHolder.setTenantDomain(tenantDomain); diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java index 0a547a0e3f..af7a890bea 100644 --- a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java +++ b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java @@ -118,10 +118,8 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor private SimpleCache, List> zoneAuthorityCache; private SimpleCache, List>> childAuthorityCache; private AuthorityBridgeTableAsynchronouslyRefreshedCache authorityBridgeTableCache; - - /** System Container ref cache (Tennant aware) */ - private Map systemContainerRefs = new ConcurrentHashMap(4); - + private SimpleCache singletonCache; // eg. for system container nodeRefs (authorityContainer and zoneContainer) + private final String KEY_SYSTEMCONTAINER_NODEREF = "key.systemcontainer.noderef"; /** Limit the number of copies of authority names floating about by keeping them in a pool **/ private ConcurrentMap authorityNamePool = new ConcurrentHashMap(); @@ -228,6 +226,11 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor this.tenantService = tenantService; } + public void setSingletonCache(SimpleCache singletonCache) + { + this.singletonCache = singletonCache; + } + public void setAclDAO(AclDAO aclDao) { this.aclDao = aclDao; @@ -338,7 +341,7 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor } authorityLookupCache.put(cacheKey(name), childRef); } - + private Pair cacheKey(String authorityName) { String tenantDomain = AuthorityType.getAuthorityType(authorityName) == AuthorityType.USER ? tenantService.getDomain(authorityName) : tenantService.getCurrentUserDomain(); @@ -350,7 +353,7 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor String pooledName = authorityNamePool.putIfAbsent(authorityName, authorityName); return pooledName == null ? authorityName : pooledName; } - + public void deleteAuthority(String name) { NodeRef nodeRef = getAuthorityOrNull(name); @@ -1242,8 +1245,8 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor */ private NodeRef getSystemContainer(QName assocQName) { - final String cacheKey = tenantService.getCurrentUserDomain() + assocQName.toString(); - NodeRef systemContainerRef = systemContainerRefs.get(cacheKey); + final String cacheKey = KEY_SYSTEMCONTAINER_NODEREF + "." + assocQName.toString(); + NodeRef systemContainerRef = (NodeRef)singletonCache.get(cacheKey); if (systemContainerRef == null) { NodeRef rootNodeRef = nodeService.getRootNode(this.storeRef); @@ -1259,7 +1262,7 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor throw new AlfrescoRuntimeException("Required path not found: " + assocQName); } systemContainerRef = results.get(0).getChildRef(); - systemContainerRefs.put(cacheKey, systemContainerRef); + singletonCache.put(cacheKey, systemContainerRef); } return systemContainerRef; } diff --git a/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizer.java b/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizer.java index 22a1d614ae..037bd11516 100644 --- a/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizer.java +++ b/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizer.java @@ -38,6 +38,8 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.tenant.Tenant; import org.alfresco.repo.tenant.TenantAdminService; import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.model.FileExistsException; @@ -178,8 +180,8 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean final String overrideProviderName = getOverrideHomeFolderProviderName(); // Scan users in default and each Tenant - String systemUserName = AuthenticationUtil.getSystemUserName(); - scanPeople(systemUserName, TenantService.DEFAULT_DOMAIN, overrideProviderName); + + scanPeople(AuthenticationUtil.getSystemUserName(), TenantService.DEFAULT_DOMAIN, overrideProviderName); if (tenantAdminService.isEnabled()) { @@ -188,8 +190,16 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean { if (tenant.isEnabled()) { - systemUserName = tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenant.getTenantDomain()); - scanPeople(systemUserName, tenant.getTenantDomain(), overrideProviderName); + final String tenantDomain = tenant.getTenantDomain(); + TenantUtil.runAsSystemTenant(new TenantRunAsWork() + { + public NodeRef doWork() throws Exception + { + scanPeople(AuthenticationUtil.getSystemUserName(), tenantDomain, overrideProviderName); + return null; + } + }, tenantDomain); + } } } @@ -206,7 +216,7 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean * in place of the all home folders existing providers. If {@code null} * the existing provider is used. */ - private void scanPeople(final String systemUserName, String tenantDomain, final String overrideProvider) + private void scanPeople(final String systemUserName, final String tenantDomain, final String overrideProvider) { /* * To avoid problems with existing home folders that are located in locations @@ -240,7 +250,7 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean final String createParentFoldersPhaseName = "createParentFolders"; RunAsWorker[] workers = new RunAsWorker[] { - new RunAsWorker(systemUserName, "calculateParentFolderStructure") + new RunAsWorker(systemUserName, tenantDomain, "calculateParentFolderStructure") { @Override public void doWork(NodeRef person) throws Exception @@ -249,8 +259,8 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean parentFolderStructure, person, overrideProvider); } }, - - new RunAsWorker(systemUserName, "moveHomeFolderThatClashesWithParentFolderStructure") + + new RunAsWorker(systemUserName, tenantDomain, "moveHomeFolderThatClashesWithParentFolderStructure") { @Override public void doWork(NodeRef person) throws Exception @@ -259,8 +269,8 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean parentFolderStructure, tmpFolders, person, overrideProvider); } }, - - new RunAsWorker(systemUserName, createParentFoldersPhaseName) + + new RunAsWorker(systemUserName, tenantDomain, createParentFoldersPhaseName) { @Override public void doWork(NodeRef person) throws Exception @@ -268,8 +278,8 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean createParentFolders(person, overrideProvider); } }, - - new RunAsWorker(systemUserName, "moveHomeFolderIfRequired") + + new RunAsWorker(systemUserName, tenantDomain, "moveHomeFolderIfRequired") { @Override public void doWork(NodeRef person) throws Exception @@ -283,6 +293,7 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean for (RunAsWorker worker: workers) { String name = worker.getName(); + if (logger.isInfoEnabled()) { logger.info(" -- "+ @@ -908,26 +919,34 @@ public class HomeFolderProviderSynchronizer extends AbstractLifecycleBean } final String userName; + final String tenantDomain; final String name; - public RunAsWorker(String userName, String name) + public RunAsWorker(String userName, String tenantDomain, String name) { this.userName = userName; + this.tenantDomain = tenantDomain; this.name = name; } public void process(final NodeRef person) throws Throwable { - RunAsWork runAsWork = new RunAsWork() + // note: runAs before runAsTenant (to avoid clearing tenant context, if no previous auth) + AuthenticationUtil.runAs(new RunAsWork() { @Override public Object doWork() throws Exception { - RunAsWorker.this.doWork(person); - return null; + return TenantUtil.runAsTenant(new TenantRunAsWork() + { + public Void doWork() throws Exception + { + RunAsWorker.this.doWork(person); + return null; + } + }, tenantDomain); } - }; - AuthenticationUtil.runAs(runAsWork, userName); + }, userName); } public abstract void doWork(NodeRef person) throws Exception; diff --git a/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizerTest.java b/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizerTest.java index 5240b4d27a..d1228a9037 100644 --- a/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizerTest.java +++ b/source/java/org/alfresco/repo/security/person/HomeFolderProviderSynchronizerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -39,6 +39,8 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.tenant.Tenant; import org.alfresco.repo.tenant.TenantAdminService; import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.model.FileFolderService; @@ -235,7 +237,6 @@ public class HomeFolderProviderSynchronizerTest nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME)); final String domainUsername = tenantService.getBaseNameUser(username); String tenantDomain = tenantService.getUserDomain(username); - String systemUser = tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain); boolean disabled = !TenantService.DEFAULT_DOMAIN.equals(tenantDomain) && !tenantAdminService.isEnabledTenant(tenantDomain); try @@ -244,14 +245,14 @@ public class HomeFolderProviderSynchronizerTest { tenantAdminService.enableTenant(tenantDomain); } - AuthenticationUtil.runAs(new RunAsWork() - { + TenantUtil.runAsSystemTenant(new TenantRunAsWork() + { public Object doWork() throws Exception { deleteUser(adminGuestUserHomeFolders, nodeRef, username, domainUsername); return null; } - }, systemUser); + }, tenantDomain); } finally { @@ -322,8 +323,7 @@ public class HomeFolderProviderSynchronizerTest final boolean createHomeDirectory) throws Exception { final String domainUsername = tenantService.getDomainUser(username, tenantDomain); - String systemUser = tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain); - return AuthenticationUtil.runAs(new RunAsWork() + return TenantUtil.runAsSystemTenant(new TenantRunAsWork() { public NodeRef doWork() throws Exception { @@ -374,7 +374,7 @@ public class HomeFolderProviderSynchronizerTest } return person; } - }, systemUser); + }, tenantDomain); } private NodeRef createFolder(String path) throws Exception @@ -538,24 +538,25 @@ public class HomeFolderProviderSynchronizerTest try { final String domainUsername = tenantService.getDomainUser(username, tenantDomain); - String systemUser = tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain); - AuthenticationUtil.runAs(new RunAsWork() + TenantUtil.runAsSystemTenant(new TenantRunAsWork() { public NodeRef doWork() throws Exception { NodeRef person = personService.getPerson(domainUsername, false); NodeRef homeFolder = DefaultTypeConverter.INSTANCE.convert(NodeRef.class, nodeService.getProperty(person, ContentModel.PROP_HOMEFOLDER)); + if (expectedPath != null) { assertNotNull("User: "+domainUsername+" home folder should exist", homeFolder); } + NodeRef rootPath = homeFolderManager.getRootPathNodeRef(largeHomeFolderProvider); String actualPath = toPath(rootPath, homeFolder); assertEquals("User: "+domainUsername+" home folder location", expectedPath, actualPath); return null; } - }, systemUser); + }, tenantDomain); } catch (RuntimeException e) { diff --git a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java index d8fb83303d..64fc45e021 100644 --- a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java +++ b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java @@ -29,7 +29,6 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; -import java.util.concurrent.ConcurrentHashMap; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; @@ -54,6 +53,8 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.permissions.PermissionServiceSPI; import org.alfresco.repo.tenant.TenantDomainMismatchException; import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; @@ -154,8 +155,9 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per /** a transactionally-safe cache to be injected */ private SimpleCache> personCache; - /** People Container ref cache (Tennant aware) */ - private Map peopleContainerRefs = new ConcurrentHashMap(4); + // note: cache is tenant-aware (if using EhCacheAdapter shared cache) + private SimpleCache singletonCache; // eg. for peopleContainerNodeRef + private final String KEY_PEOPLECONTAINER_NODEREF = "key.peoplecontainer.noderef"; private UserNameMatcher userNameMatcher; @@ -238,7 +240,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per ContentModel.TYPE_USER, new JavaBehaviour(this, "onUpdatePropertiesUser")); } - + /** * {@inheritDoc} */ @@ -281,17 +283,22 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per { this.serviceRegistry = serviceRegistry; } - + public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; } - + public void setTenantService(TenantService tenantService) { this.tenantService = tenantService; } - + + public void setSingletonCache(SimpleCache singletonCache) + { + this.singletonCache = singletonCache; + } + public void setSearchService(SearchService searchService) { this.searchService = searchService; @@ -440,13 +447,13 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per { final String tenantDomain = tenantService.getUserDomain(userName); - return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + return TenantUtil.runAsSystemTenant(new TenantRunAsWork() { public NodeRef doWork() throws Exception { return getPersonImpl(userName, autoCreateHomeFolderAndMissingPersonIfAllowed, exceptionOrNull); } - }, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); + }, tenantDomain); } else { @@ -1068,33 +1075,32 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per */ public NodeRef getPeopleContainer() { - String cacheKey = tenantService.getCurrentUserDomain(); - NodeRef peopleNodeRef = peopleContainerRefs.get(cacheKey); + NodeRef peopleNodeRef = (NodeRef)singletonCache.get(KEY_PEOPLECONTAINER_NODEREF); if (peopleNodeRef == null) { - NodeRef rootNodeRef = nodeService.getRootNode(tenantService.getName(storeRef)); + NodeRef rootNodeRef = nodeService.getRootNode(storeRef); List children = nodeService.getChildAssocs(rootNodeRef, RegexQNamePattern.MATCH_ALL, QName.createQName(SYSTEM_FOLDER_SHORT_QNAME, namespacePrefixResolver), false); - + if (children.size() == 0) { throw new AlfrescoRuntimeException("Required people system path not found: " + SYSTEM_FOLDER_SHORT_QNAME); } - + NodeRef systemNodeRef = children.get(0).getChildRef(); - + children = nodeService.getChildAssocs(systemNodeRef, RegexQNamePattern.MATCH_ALL, QName.createQName( PEOPLE_FOLDER_SHORT_QNAME, namespacePrefixResolver), false); - + if (children.size() == 0) { throw new AlfrescoRuntimeException("Required people system path not found: " + PEOPLE_FOLDER_SHORT_QNAME); } - + peopleNodeRef = children.get(0).getChildRef(); - peopleContainerRefs.put(cacheKey, peopleNodeRef); + singletonCache.put(KEY_PEOPLECONTAINER_NODEREF, peopleNodeRef); } return peopleNodeRef; } @@ -1113,7 +1119,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per NodeRef personRef = getPersonOrNullImpl(userName); - deletePersonImpl(userName, personRef); + deletePersonAndAuthenticationImpl(userName, personRef); } /** @@ -1125,7 +1131,31 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per if (typeQName.equals(ContentModel.TYPE_PERSON)) { String userName = (String) this.nodeService.getProperty(personRef, ContentModel.PROP_USERNAME); - deletePersonImpl(userName, personRef); + deletePersonAndAuthenticationImpl(userName, personRef); + } + else + { + throw new AlfrescoRuntimeException("deletePerson: invalid type of node "+personRef+" (actual="+typeQName+", expected="+ContentModel.TYPE_PERSON+")"); + } + } + + /** + * {@inheritDoc} + */ + public void deletePerson(NodeRef personRef, boolean deleteAuthentication) + { + QName typeQName = nodeService.getType(personRef); + if (typeQName.equals(ContentModel.TYPE_PERSON)) + { + if (deleteAuthentication) + { + String userName = (String) this.nodeService.getProperty(personRef, ContentModel.PROP_USERNAME); + deletePersonAndAuthenticationImpl(userName, personRef); + } + else + { + deletePersonImpl(personRef); + } } else { @@ -1133,7 +1163,8 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per } } - private void deletePersonImpl(String userName, NodeRef personRef) + + private void deletePersonAndAuthenticationImpl(String userName, NodeRef personRef) { if (userName != null) { @@ -1161,6 +1192,11 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per permissionServiceSPI.deletePermissions(userName); } + deletePersonImpl(personRef); + } + + private void deletePersonImpl(NodeRef personRef) + { // delete the person if (personRef != null) { @@ -1185,7 +1221,6 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per { AlfrescoTransactionSupport.bindListener(this); } - } /** diff --git a/source/java/org/alfresco/repo/site/SiteServiceImpl.java b/source/java/org/alfresco/repo/site/SiteServiceImpl.java index 4467f02630..72900cbced 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceImpl.java +++ b/source/java/org/alfresco/repo/site/SiteServiceImpl.java @@ -30,7 +30,6 @@ import java.util.Set; import java.util.SortedSet; import java.util.StringTokenizer; import java.util.TreeSet; -import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -43,6 +42,7 @@ import org.alfresco.query.PagingRequest; import org.alfresco.query.PagingResults; import org.alfresco.repo.activities.ActivityType; import org.alfresco.repo.admin.SysAdminParams; +import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.node.NodeServicePolicies.OnRestoreNodePolicy; import org.alfresco.repo.node.getchildren.FilterProp; @@ -58,8 +58,9 @@ import org.alfresco.repo.security.authentication.AuthenticationContext; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.permissions.AccessDeniedException; -import org.alfresco.repo.tenant.TenantAdminService; import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.activities.ActivityService; @@ -125,12 +126,13 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic private static final int GROUP_PREFIX_LENGTH = PermissionService.GROUP_PREFIX.length(); private static final int GROUP_SITE_PREFIX_LENGTH = GROUP_SITE_PREFIX.length(); - /** Site home ref cache (Tennant aware) */ - private Map siteHomeRefs = new ConcurrentHashMap(4); + // note: caches are tenant-aware (if using EhCacheAdapter shared cache) + + private SimpleCache singletonCache; // eg. for siteHomeNodeRef + private final String KEY_SITEHOME_NODEREF = "key.sitehome.noderef"; + + private SimpleCache siteNodeRefCache; // for site shortname to nodeRef lookup - /** Site node ref cache (Tennant aware) */ - private Map siteNodeRefs = new ConcurrentHashMap(256); - private String sitesXPath; /** Messages */ @@ -144,7 +146,7 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic private static final String MSG_CAN_NOT_CHANGE_MSHIP="site_service.can_not_change_membership"; private static final String MSG_SITE_CONTAINER_NOT_FOLDER = "site_service.site_container_not_folder"; private static final String MSG_INVALID_SITE_TYPE = "site_service.invalid_site_type"; - + /* Services */ private NodeService nodeService; private NodeService directNodeService; @@ -159,7 +161,6 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic private AuthorityService authorityService; private DictionaryService dictionaryService; private TenantService tenantService; - private TenantAdminService tenantAdminService; private RetryingTransactionHelper retryingTransactionHelper; private Comparator roleComparator; private SysAdminParams sysAdminParams; @@ -292,12 +293,14 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic this.tenantService = tenantService; } - /** - * Sets the tenant admin service - */ - public void setTenantAdminService(TenantAdminService tenantAdminService) + public void setSingletonCache(SimpleCache singletonCache) { - this.tenantAdminService = tenantAdminService; + this.singletonCache = singletonCache; + } + + public void setSiteNodeRefCache(SimpleCache siteNodeRefCache) + { + this.siteNodeRefCache = siteNodeRefCache; } /** @@ -745,8 +748,7 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic */ public NodeRef getSiteRoot() { - String tenantDomain = tenantAdminService.getCurrentUserDomain(); - NodeRef siteHomeRef = siteHomeRefs.get(tenantDomain); + NodeRef siteHomeRef = (NodeRef)singletonCache.get(KEY_SITEHOME_NODEREF); if (siteHomeRef == null) { siteHomeRef = AuthenticationUtil.runAs(new RunAsWork() @@ -782,7 +784,7 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic // There may be domains with no sites (e.g. JSF-only clients). if (siteHomeRef != null) { - siteHomeRefs.put(tenantDomain, siteHomeRef); + singletonCache.put(KEY_SITEHOME_NODEREF, siteHomeRef); } } return siteHomeRef; @@ -930,13 +932,13 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic { final String tenantDomain = tenantService.getUserDomain(userName); - return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork>() - { - public List doWork() throws Exception + return TenantUtil.runAsSystemTenant(new TenantRunAsWork>() { - return listSitesImpl(userName, size); - } - }, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); + public List doWork() throws Exception + { + return listSitesImpl(userName, size); + } + }, tenantDomain); } else { @@ -1167,14 +1169,14 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic final String tenantDomain = tenantService.getDomain(shortName); final String sName = tenantService.getBaseName(shortName, true); - return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + return TenantUtil.runAsSystemTenant(new TenantRunAsWork() { public SiteInfo doWork() throws Exception { SiteInfo site = getSiteImpl(sName); return new SiteInfoImpl(site.getSitePreset(), shortName, site.getTitle(), site.getDescription(), site.getVisibility(), site.getCustomProperties(), site.getNodeRef()); } - }, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); + }, tenantDomain); } else { @@ -1266,14 +1268,13 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic */ private NodeRef getSiteNodeRef(final String shortName, boolean enforcePermissions) { - final String cacheKey = this.tenantAdminService.getCurrentUserDomain() + '_' + shortName; - NodeRef siteNodeRef = this.siteNodeRefs.get(cacheKey); + NodeRef siteNodeRef = siteNodeRefCache.get(shortName); if (siteNodeRef != null) { // test for existance - and remove from cache if no longer exists if (!this.directNodeService.exists(siteNodeRef)) { - this.siteNodeRefs.remove(cacheKey); + siteNodeRefCache.remove(shortName); siteNodeRef = null; } } @@ -1292,7 +1293,7 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic // cache the result if found - null results will be required to ensure new sites are found later if (siteNode != null) { - siteNodeRefs.put(cacheKey, siteNode); + siteNodeRefCache.put(shortName, siteNode); } return siteNode; } @@ -1309,7 +1310,7 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic return siteNodeRef; } } - + /** * @see org.alfresco.service.cmr.site.SiteService#updateSite(org.alfresco.service.cmr.site.SiteInfo) */ @@ -1417,10 +1418,9 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic throw new SiteServiceException(MSG_CAN_NOT_DELETE, new Object[]{shortName}); } final QName siteType = this.directNodeService.getType(siteNodeRef); - + // Delete the cached reference - String cacheKey = this.tenantAdminService.getCurrentUserDomain() + '_' + shortName; - this.siteNodeRefs.remove(cacheKey); + siteNodeRefCache.remove(shortName); // Collection for recording the group memberships present on the site final Map> groupsMemberships = new HashMap>(); @@ -1515,13 +1515,13 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic final String tenantDomain = tenantService.getDomain(shortName); final String sName = tenantService.getBaseName(shortName, true); - return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork>() + return TenantUtil.runAsSystemTenant(new TenantRunAsWork>() { public Map doWork() throws Exception { return listMembersImpl(sName, nameFilter, roleFilter, size, collapseGroups); } - }, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); + }, tenantDomain); } else { diff --git a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java index 5bf1fd1460..e9ce9e0846 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java +++ b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -21,6 +21,7 @@ package org.alfresco.repo.tenant; import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; +import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Properties; @@ -42,8 +43,9 @@ import org.alfresco.repo.node.db.DbNodeServiceImpl; import org.alfresco.repo.security.authentication.AuthenticationContext; import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; import org.alfresco.repo.thumbnail.ThumbnailRegistry; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.usage.UserUsageTrackingComponent; import org.alfresco.repo.workflow.WorkflowDeployer; import org.alfresco.service.cmr.admin.RepoAdminService; @@ -74,7 +76,7 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo private static Log logger = LogFactory.getLog(MultiTAdminServiceImpl.class); // Keep hold of the app context - private ApplicationContext ctx; + protected ApplicationContext ctx; // Dependencies private NodeService nodeService; @@ -89,6 +91,8 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo protected ContentStore tenantFileContentStore; private ThumbnailRegistry thumbnailRegistry; + private String contentRootContainerPath = null; + private WorkflowService workflowService; private RepositoryExporterService repositoryExporterService; private ModuleService moduleService; @@ -96,6 +100,9 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo private String baseAdminUsername = null; + // Experimental: Thor + private TenantRoutingDataSource trds; + /* * Tenant domain/ids are unique strings that are case-insensitive. Tenant ids must be valid filenames. * They may also map onto domains and hence should allow valid FQDN. @@ -161,17 +168,17 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo { this.tenantAdminDAO = tenantAdminDAO; } - + public void setPasswordEncoder(PasswordEncoder passwordEncoder) { this.passwordEncoder = passwordEncoder; } - + public void setTenantFileContentStore(ContentStore tenantFileContentStore) { this.tenantFileContentStore = tenantFileContentStore; } - + public void setWorkflowService(WorkflowService workflowService) { this.workflowService = workflowService; @@ -206,6 +213,17 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo this.baseAdminUsername = baseAdminUsername; } + public void setTenantRoutingDataSource(TenantRoutingDataSource trds) + { + this.trds = trds; + } + + // if set then tenant are not co-mingled and all content roots will appear below this container (in sub-folder) + public void setContentRootContainerPath(String contentRootContainerPath) + { + this.contentRootContainerPath = contentRootContainerPath; + } + public static final String PROTOCOL_STORE_USER = "user"; public static final String PROTOCOL_STORE_WORKSPACE = "workspace"; public static final String PROTOCOL_STORE_SYSTEM = "system"; @@ -219,6 +237,7 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo public static final String TENANTS_ATTRIBUTE_PATH = "alfresco-tenants"; public static final String TENANT_ATTRIBUTE_ENABLED = "enabled"; public static final String TENANT_ATTRIBUTE_ROOT_CONTENT_STORE_DIR = "rootContentStoreDir"; + public static final String TENANT_ATTRIBUTE_DB_URL = "dbUrl"; // if not co-mingled private List tenantDeployers = new ArrayList(); @@ -350,17 +369,59 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo /** * @see TenantAdminService.createTenant() */ - public void createTenant(String tenantDomainIn, final char[] tenantAdminRawPassword, String rootContentStoreDir) + public void createTenant(final String tenantDomain, final char[] tenantAdminRawPassword, String contentRoot) + { + createTenant(tenantDomain, tenantAdminRawPassword, contentRoot, null); + } + + /** + * @see TenantAdminService.createTenant() + */ + public void createTenant(final String tenantDomainIn, final char[] tenantAdminRawPassword, String contentRootPath, String dbUrl) { ParameterCheck.mandatory("tenantAdminRawPassword", tenantAdminRawPassword); final String tenantDomain = getTenantDomain(tenantDomainIn); - + AuthenticationUtil.setMtEnabled(true); // in case this is the 1st tenant long start = System.currentTimeMillis(); + + if ((contentRootContainerPath != null) && (! contentRootContainerPath.isEmpty())) + { + String defaultContentRoot = null; + + if (! contentRootContainerPath.endsWith("/")) + { + defaultContentRoot = contentRootContainerPath + "/" + tenantDomain; + } + else + { + defaultContentRoot = contentRootContainerPath + tenantDomain; + } + + if ((contentRootPath != null) && (! contentRootPath.isEmpty())) + { + logger.warn("Use default content root path: "+defaultContentRoot+" (ignoring: "+contentRootPath+")"); + } + + contentRootPath = defaultContentRoot; + } - initTenant(tenantDomain, rootContentStoreDir); + initTenant(tenantDomain, contentRootPath, dbUrl); + + if ((dbUrl != null) && (trds != null)) + { + try + { + // note: experimental - currently assumes a bootstrapped DB schema exists for this dbUrl ! + trds.addTenantDataSource(tenantDomain, dbUrl); + } + catch (SQLException se) + { + throw new AlfrescoRuntimeException("Failed to create tenant '"+tenantDomain+"' for dbUrl '"+dbUrl+"'", se); + } + } try { @@ -375,42 +436,58 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo ((TenantDeployer)tenantFileContentStore).init(); } - // create tenant-specific stores - ImporterBootstrap userImporterBootstrap = (ImporterBootstrap)ctx.getBean("userBootstrap-mt"); - bootstrapUserTenantStore(userImporterBootstrap, tenantDomain, tenantAdminRawPassword); - - ImporterBootstrap systemImporterBootstrap = (ImporterBootstrap)ctx.getBean("systemBootstrap-mt"); - bootstrapSystemTenantStore(systemImporterBootstrap, tenantDomain); - - // deprecated - ImporterBootstrap versionImporterBootstrap = (ImporterBootstrap)ctx.getBean("versionBootstrap-mt"); - bootstrapVersionTenantStore(versionImporterBootstrap, tenantDomain); - - ImporterBootstrap version2ImporterBootstrap = (ImporterBootstrap)ctx.getBean("version2Bootstrap-mt"); - bootstrapVersionTenantStore(version2ImporterBootstrap, tenantDomain); - - ImporterBootstrap spacesArchiveImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesArchiveBootstrap-mt"); - bootstrapSpacesArchiveTenantStore(spacesArchiveImporterBootstrap, tenantDomain); - - ImporterBootstrap spacesImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesBootstrap-mt"); - bootstrapSpacesTenantStore(spacesImporterBootstrap, tenantDomain); - - thumbnailRegistry.initThumbnailDefinitions(); - - // notify listeners that tenant has been created & hence enabled - for (TenantDeployer tenantDeployer : tenantDeployers) + // callback + RetryingTransactionCallback doImportCallback = new RetryingTransactionCallback() { - tenantDeployer.onEnableTenant(); - } + public Object execute() throws Throwable + { + // create tenant-specific stores + ImporterBootstrap userImporterBootstrap = (ImporterBootstrap)ctx.getBean("userBootstrap-mt"); + bootstrapUserTenantStore(userImporterBootstrap, tenantDomain, tenantAdminRawPassword); + + ImporterBootstrap systemImporterBootstrap = (ImporterBootstrap)ctx.getBean("systemBootstrap-mt"); + bootstrapSystemTenantStore(systemImporterBootstrap, tenantDomain); + + // deprecated + ImporterBootstrap versionImporterBootstrap = (ImporterBootstrap)ctx.getBean("versionBootstrap-mt"); + bootstrapVersionTenantStore(versionImporterBootstrap, tenantDomain); + + ImporterBootstrap version2ImporterBootstrap = (ImporterBootstrap)ctx.getBean("version2Bootstrap-mt"); + bootstrapVersionTenantStore(version2ImporterBootstrap, tenantDomain); + + ImporterBootstrap spacesArchiveImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesArchiveBootstrap-mt"); + bootstrapSpacesArchiveTenantStore(spacesArchiveImporterBootstrap, tenantDomain); + + ImporterBootstrap spacesImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesBootstrap-mt"); + bootstrapSpacesTenantStore(spacesImporterBootstrap, tenantDomain); + + thumbnailRegistry.initThumbnailDefinitions(); + + // TODO janv - resolve this conflict later + /* Note: assume for now that all tenant deployers can lazily init + + // notify listeners that tenant has been created & hence enabled + for (TenantDeployer tenantDeployer : tenantDeployers) + { + tenantDeployer.onEnableTenant(); + } + */ + + // bootstrap workflows + for (WorkflowDeployer workflowDeployer : workflowDeployers) + { + workflowDeployer.init(); + } + + // bootstrap modules (if any) + moduleService.startModules(); + + return null; + } + }; - // bootstrap workflows - for (WorkflowDeployer workflowDeployer : workflowDeployers) - { - workflowDeployer.init(); - } - - // bootstrap modules (if any) - moduleService.startModules(); + // if not default DB (ie. dbUrl != null) then run in new Spring managed txn (to ensure datasource is switched) + transactionService.getRetryingTransactionHelper().doInTransaction(doImportCallback, transactionService.isReadOnly(), (dbUrl != null)); } finally { @@ -426,20 +503,23 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo /** * Export tenant - equivalent to the tenant admin running a 'complete repo' export from the Web Client Admin */ - public void exportTenant(String tenantDomain, final File directoryDestination) + public void exportTenant(String tenantDomainIn, final File directoryDestination) { - final String lowerTenantDomain = getTenantDomain(tenantDomain); - - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() - { - repositoryExporterService.export(directoryDestination, lowerTenantDomain); - return null; - } - }, getSystemUser(tenantDomain)); + final String tenantDomain = getTenantDomain(tenantDomainIn); - logger.info("Tenant exported: " + tenantDomain); + TenantUtil.runAsSystemTenant(new TenantRunAsWork() + { + public Object doWork() + { + repositoryExporterService.export(directoryDestination, tenantDomain); + return null; + } + }, tenantDomain); + + if (logger.isInfoEnabled()) + { + logger.info("Tenant exported: " + tenantDomain); + } } /** @@ -451,7 +531,7 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo AuthenticationUtil.setMtEnabled(true); // in case this is the 1st tenant - initTenant(tenantDomain, contentRoot); + initTenant(tenantDomain, contentRoot, null); try { @@ -489,14 +569,17 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo } // bootstrap modules (if any) - moduleService.startModules(); + moduleService.startModules(); } finally { AuthenticationUtil.popAuthentication(); } - - logger.info("Tenant imported: " + tenantDomain); + + if (logger.isInfoEnabled()) + { + logger.info("Tenant imported: " + tenantDomain); + } } public boolean existsTenant(String tenantDomain) @@ -505,7 +588,7 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo ParameterCheck.mandatory("tenantDomain", tenantDomain); tenantDomain = getTenantDomain(tenantDomain); - + return (getTenantAttributes(tenantDomain) != null); } @@ -518,15 +601,15 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo } else { - Tenant tenant = new Tenant(tenantEntity.getTenantDomain(), tenantEntity.getEnabled(), tenantEntity.getContentRoot()); + Tenant tenant = new Tenant(tenantEntity.getTenantDomain(), tenantEntity.getEnabled(), tenantEntity.getContentRoot(), null); return tenant; } } public void enableTenant(String tenantDomain) { - tenantDomain = getTenantDomain(tenantDomain); - + tenantDomain = getTenantDomain(tenantDomain); + if (! existsTenant(tenantDomain)) { throw new AuthenticationException("Tenant does not exist: " + tenantDomain); @@ -537,11 +620,13 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo logger.warn("Tenant already enabled: " + tenantDomain); } - enableTenant(tenantDomain, true); + // Note: assume for now that all tenant deployers can lazily init + boolean notifyTenantDeployers = false; + enableTenant(tenantDomain, notifyTenantDeployers); } - private void enableTenant(String tenantDomain, boolean notifyTenantDeployers) - { + protected void enableTenant(String tenantDomain, boolean notifyTenantDeployers) + { // Check that all the passed values are not null ParameterCheck.mandatory("tenantDomain", tenantDomain); @@ -552,26 +637,29 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo if (notifyTenantDeployers) { // notify listeners that tenant has been enabled - AuthenticationUtil.runAs(new RunAsWork() + TenantUtil.runAsSystemTenant(new TenantRunAsWork() + { + public Object doWork() + { + for (TenantDeployer tenantDeployer : tenantDeployers) { - public Object doWork() - { - for (TenantDeployer tenantDeployer : tenantDeployers) - { - tenantDeployer.onEnableTenant(); - } - return null; - } - }, getSystemUser(tenantDomain)); + tenantDeployer.onEnableTenant(); + } + return null; + } + }, tenantDomain); } - logger.info("Tenant enabled: " + tenantDomain); - } + if (logger.isInfoEnabled()) + { + logger.info("Tenant enabled: " + tenantDomain); + } + } public void disableTenant(String tenantDomain) { - tenantDomain = getTenantDomain(tenantDomain); - + tenantDomain = getTenantDomain(tenantDomain); + if (! existsTenant(tenantDomain)) { throw new AuthenticationException("Tenant does not exist: " + tenantDomain); @@ -585,24 +673,24 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo disableTenant(tenantDomain, true); } - public void disableTenant(String tenantDomain, boolean notifyTenantDeployers) + protected void disableTenant(String tenantDomain, boolean notifyTenantDeployers) { - tenantDomain = getTenantDomain(tenantDomain); - + tenantDomain = getTenantDomain(tenantDomain); + if (notifyTenantDeployers) { // notify listeners that tenant has been disabled - AuthenticationUtil.runAs(new RunAsWork() + TenantUtil.runAsSystemTenant(new TenantRunAsWork() + { + public Object doWork() + { + for (TenantDeployer tenantDeployer : tenantDeployers) { - public Object doWork() - { - for (TenantDeployer tenantDeployer : tenantDeployers) - { - tenantDeployer.onDisableTenant(); - } - return null; - } - }, getSystemUser(tenantDomain)); + tenantDeployer.onDisableTenant(); + } + return null; + } + }, tenantDomain); } // update tenant attributes / tenant cache - need to disable after notifying listeners (else they cannot disable) @@ -610,27 +698,30 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo tenantUpdateEntity.setEnabled(false); tenantAdminDAO.updateTenant(tenantUpdateEntity); - logger.info("Tenant disabled: " + tenantDomain); + if (logger.isInfoEnabled()) + { + logger.info("Tenant disabled: " + tenantDomain); + } } public boolean isEnabledTenant(String tenantDomain) { // Check that all the passed values are not null ParameterCheck.mandatory("tenantDomain", tenantDomain); - + tenantDomain = getTenantDomain(tenantDomain); - + Tenant tenant = getTenantAttributes(tenantDomain); if (tenant != null) { return tenant.isEnabled(); } - + return false; } protected String getRootContentStoreDir(String tenantDomain) - { + { // Check that all the passed values are not null ParameterCheck.mandatory("tenantDomain", tenantDomain); @@ -645,13 +736,13 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo public Tenant getTenant(String tenantDomain) { - tenantDomain = getTenantDomain(tenantDomain); + tenantDomain = getTenantDomain(tenantDomain); if (! existsTenant(tenantDomain)) { throw new AuthenticationException("Tenant does not exist: " + tenantDomain); } - return new Tenant(tenantDomain, isEnabledTenant(tenantDomain), getRootContentStoreDir(tenantDomain)); + return getTenantAttributes(tenantDomain); } /** @@ -659,17 +750,17 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo */ public void deleteTenant(String tenantDomain) { - tenantDomain = getTenantDomain(tenantDomain); - + tenantDomain = getTenantDomain(tenantDomain); + if (! existsTenant(tenantDomain)) { throw new AuthenticationException("Tenant does not exist: " + tenantDomain); } else { - try + try { - AuthenticationUtil.runAs(new RunAsWork() + TenantUtil.runAsSystemTenant(new TenantRunAsWork() { public Object doWork() { @@ -702,7 +793,7 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo return null; } - }, getSystemUser(tenantDomain)); + }, tenantDomain); final String tenantAdminUser = getTenantAdminUser(tenantDomain); @@ -714,19 +805,22 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_SYSTEM, STORE_BASE_ID_SYSTEM))); nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_USER, STORE_BASE_ID_USER))); - - // notify listeners that tenant has been deleted & hence disabled - AuthenticationUtil.runAs(new RunAsWork() + TenantUtil.runAsSystemTenant(new TenantRunAsWork() { public Object doWork() { + // shutdown modules (if any) + moduleService.shutdownModules(); + + // notify listeners that tenant has been deleted & hence disabled for (TenantDeployer tenantDeployer : tenantDeployers) { tenantDeployer.onDisableTenant(); } + return null; } - }, getSystemUser(tenantDomain)); + }, tenantDomain); // remove tenant tenantAdminDAO.deleteTenant(tenantDomain); @@ -752,7 +846,7 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo List tenants = new ArrayList(tenantEntities.size()); for (TenantEntity tenantEntity : tenantEntities) { - tenants.add(new Tenant(tenantEntity.getTenantDomain(), tenantEntity.getEnabled(), tenantEntity.getContentRoot())); + tenants.add(new Tenant(tenantEntity.getTenantDomain(), tenantEntity.getEnabled(), tenantEntity.getContentRoot(), null)); } return tenants; } @@ -773,7 +867,7 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo bootstrapSystemTenantStore(systemImporterBootstrap, tenantDomain); } - private void bootstrapSystemTenantStore(ImporterBootstrap systemImporterBootstrap, String tenantDomain) + protected void bootstrapSystemTenantStore(ImporterBootstrap systemImporterBootstrap, String tenantDomain) { // Bootstrap Tenant-Specific System Store StoreRef bootstrapStoreRef = systemImporterBootstrap.getStoreRef(); @@ -790,7 +884,10 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo // reset since systemImporter is singleton (hence reused) systemImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); - logger.debug("Bootstrapped store: " + tenantService.getBaseName(tenantBootstrapStoreRef)); + if (logger.isDebugEnabled()) + { + logger.debug("Bootstrapped store: "+tenantService.getBaseName(tenantBootstrapStoreRef)+" (Tenant: "+tenantDomain+")"); + } } private void importBootstrapUserTenantStore(String tenantDomain, File directorySource) @@ -809,54 +906,60 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo bootstrapUserTenantStore(userImporterBootstrap, tenantDomain, null); } - private void bootstrapUserTenantStore(ImporterBootstrap userImporterBootstrap, String tenantDomain, char[] tenantAdminRawPassword) + protected void bootstrapUserTenantStore(ImporterBootstrap userImporterBootstrap, String tenantDomain, char[] tenantAdminRawPassword) { // Bootstrap Tenant-Specific User Store StoreRef bootstrapStoreRef = userImporterBootstrap.getStoreRef(); bootstrapStoreRef = new StoreRef(bootstrapStoreRef.getProtocol(), tenantService.getName(bootstrapStoreRef.getIdentifier(), tenantDomain)); userImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); - + // override admin username property - Properties props = userImporterBootstrap.getConfiguration(); + Properties props = userImporterBootstrap.getConfiguration(); props.put("alfresco_user_store.adminusername", getTenantAdminUser(tenantDomain)); if (tenantAdminRawPassword != null) { String salt = null; // GUID.generate(); - props.put("alfresco_user_store.adminpassword", passwordEncoder.encodePassword(new String(tenantAdminRawPassword), salt)); + props.put("alfresco_user_store.adminpassword", passwordEncoder.encodePassword(new String(tenantAdminRawPassword), salt)); } userImporterBootstrap.bootstrap(); - logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); + if (logger.isDebugEnabled()) + { + logger.debug("Bootstrapped store: "+tenantService.getBaseName(bootstrapStoreRef)+" (Tenant: "+tenantDomain+")"); + } } - + private void importBootstrapVersionTenantStore(String tenantDomain, File directorySource) { // Import Bootstrap (restore) Tenant-Specific Version Store Properties bootstrapView = new Properties(); bootstrapView.put("path", "/"); bootstrapView.put("location", directorySource.getPath()+"/"+tenantDomain+"_versions2.acp"); - + List bootstrapViews = new ArrayList(1); bootstrapViews.add(bootstrapView); ImporterBootstrap versionImporterBootstrap = (ImporterBootstrap)ctx.getBean("versionBootstrap"); versionImporterBootstrap.setBootstrapViews(bootstrapViews); - + bootstrapVersionTenantStore(versionImporterBootstrap, tenantDomain); } - private void bootstrapVersionTenantStore(ImporterBootstrap versionImporterBootstrap, String tenantDomain) + protected void bootstrapVersionTenantStore(ImporterBootstrap versionImporterBootstrap, String tenantDomain) { // Bootstrap Tenant-Specific Version Store StoreRef bootstrapStoreRef = versionImporterBootstrap.getStoreRef(); bootstrapStoreRef = new StoreRef(bootstrapStoreRef.getProtocol(), tenantService.getName(bootstrapStoreRef.getIdentifier(), tenantDomain)); versionImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); - + versionImporterBootstrap.bootstrap(); - logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); + if (logger.isDebugEnabled()) + { + logger.debug("Bootstrapped store: "+tenantService.getBaseName(bootstrapStoreRef)+" (Tenant: "+tenantDomain+")"); + } } private void importBootstrapSpacesArchiveTenantStore(String tenantDomain, File directorySource) @@ -865,17 +968,17 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo Properties bootstrapView = new Properties(); bootstrapView.put("path", "/"); bootstrapView.put("location", directorySource.getPath()+"/"+tenantDomain+"_spaces_archive.acp"); - + List bootstrapViews = new ArrayList(1); bootstrapViews.add(bootstrapView); ImporterBootstrap spacesArchiveImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesArchiveBootstrap"); spacesArchiveImporterBootstrap.setBootstrapViews(bootstrapViews); - + bootstrapSpacesArchiveTenantStore(spacesArchiveImporterBootstrap, tenantDomain); } - private void bootstrapSpacesArchiveTenantStore(ImporterBootstrap spacesArchiveImporterBootstrap, String tenantDomain) + protected void bootstrapSpacesArchiveTenantStore(ImporterBootstrap spacesArchiveImporterBootstrap, String tenantDomain) { // Bootstrap Tenant-Specific Spaces Archive Store StoreRef bootstrapStoreRef = spacesArchiveImporterBootstrap.getStoreRef(); @@ -885,11 +988,14 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo // override default property (archive://SpacesStore) List mustNotExistStoreUrls = new ArrayList(); mustNotExistStoreUrls.add(bootstrapStoreRef.toString()); - spacesArchiveImporterBootstrap.setMustNotExistStoreUrls(mustNotExistStoreUrls); + spacesArchiveImporterBootstrap.setMustNotExistStoreUrls(mustNotExistStoreUrls); spacesArchiveImporterBootstrap.bootstrap(); - logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); + if (logger.isDebugEnabled()) + { + logger.debug("Bootstrapped store: "+tenantService.getBaseName(bootstrapStoreRef)+" (Tenant: "+tenantDomain+")"); + } } private void importBootstrapSpacesModelsTenantStore(String tenantDomain, File directorySource) @@ -927,7 +1033,7 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo bootstrapSpacesTenantStore(spacesImporterBootstrap, tenantDomain); } - private void bootstrapSpacesTenantStore(ImporterBootstrap spacesImporterBootstrap, String tenantDomain) + protected void bootstrapSpacesTenantStore(ImporterBootstrap spacesImporterBootstrap, String tenantDomain) { // Bootstrap Tenant-Specific Spaces Store StoreRef bootstrapStoreRef = spacesImporterBootstrap.getStoreRef(); @@ -940,14 +1046,17 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo // override guest username property props.put("alfresco_user_store.guestusername", getTenantGuestUser(tenantDomain)); - + spacesImporterBootstrap.bootstrap(); // calculate any missing usages UserUsageTrackingComponent userUsageTrackingComponent = (UserUsageTrackingComponent)ctx.getBean("userUsageTrackingComponent"); userUsageTrackingComponent.bootstrapInternal(); - - logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); + + if (logger.isDebugEnabled()) + { + logger.debug("Bootstrapped store: "+tenantService.getBaseName(bootstrapStoreRef)+" (Tenant: "+tenantDomain+")"); + } } public void deployTenants(final TenantDeployer deployer, Log logger) @@ -991,15 +1100,15 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo try { // deploy within context of tenant domain - AuthenticationUtil.runAs(new RunAsWork() + TenantUtil.runAsSystemTenant(new TenantRunAsWork() { public Object doWork() - { + { // init the service within tenant context deployer.init(); return null; } - }, getSystemUser(tenant.getTenantDomain())); + }, tenant.getTenantDomain()); } catch (Throwable e) @@ -1022,22 +1131,22 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo if (deployer == null) { throw new AlfrescoRuntimeException("Deployer must be provided"); - } + } if (logger == null) { throw new AlfrescoRuntimeException("Logger must be provided"); - } - + } + if (tenantService.isEnabled()) - { - UserTransaction userTransaction = transactionService.getUserTransaction(); + { + UserTransaction userTransaction = transactionService.getUserTransaction(); authenticationContext.setSystemUserAsCurrentUser(); - - List tenants = null; - try + + List tenants = null; + try { - userTransaction.begin(); - tenants = getAllTenants(); + userTransaction.begin(); + tenants = getAllTenants(); userTransaction.commit(); } catch(Throwable e) @@ -1047,28 +1156,27 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo try {authenticationContext.clearCurrentSecurityContext(); } catch (Exception ex) {} throw new AlfrescoRuntimeException("Failed to get tenants", e); } - - try + + try { AuthenticationUtil.pushAuthentication(); for (Tenant tenant : tenants) - { - if (tenant.isEnabled()) + { + if (tenant.isEnabled()) { try { // undeploy within context of tenant domain - AuthenticationUtil.runAs(new RunAsWork() + TenantUtil.runAsSystemTenant(new TenantRunAsWork() { public Object doWork() - { + { // destroy the service within tenant context deployer.destroy(); return null; - } - }, getSystemUser(tenant.getTenantDomain())); - - } + } + }, tenant.getTenantDomain()); + } catch (Throwable e) { logger.error("Undeployment failed" + e); @@ -1083,11 +1191,11 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo } } finally - { + { AuthenticationUtil.popAuthentication(); } } - } + } public void register(TenantDeployer deployer) { @@ -1159,7 +1267,7 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo } } - private void initTenant(String tenantDomain, String rootContentStoreDir) + protected void initTenant(String tenantDomain, String contentRoot, String dbUrl) { validateTenantName(tenantDomain); @@ -1168,7 +1276,7 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo throw new AlfrescoRuntimeException("Tenant already exists: " + tenantDomain); } - if (rootContentStoreDir != null) + if (contentRoot != null) { if (! (tenantFileContentStore instanceof AbstractTenantRoutingContentStore)) { @@ -1176,22 +1284,23 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo throw new AlfrescoRuntimeException("MT: cannot initialse tenant - TenantRoutingContentStore is not configured AND tenant is not using co-mingled content store (ie. default root location)"); } - File tenantRootDir = new File(rootContentStoreDir); + File tenantRootDir = new File(contentRoot); if ((tenantRootDir.exists()) && (tenantRootDir.list().length != 0)) { - logger.warn("Tenant root directory is not empty: " + rootContentStoreDir); + logger.warn("Tenant root directory is not empty: " + contentRoot); } } - if (rootContentStoreDir == null) + if (contentRoot == null) { - rootContentStoreDir = tenantFileContentStore.getRootLocation(); + contentRoot = tenantFileContentStore.getRootLocation(); } // init - need to enable tenant (including tenant service) before stores bootstrap TenantEntity tenantEntity = new TenantEntity(tenantDomain); tenantEntity.setEnabled(true); - tenantEntity.setContentRoot(rootContentStoreDir); + tenantEntity.setContentRoot(contentRoot); + tenantEntity.setDbUrl(dbUrl); tenantAdminDAO.createTenant(tenantEntity); } @@ -1254,14 +1363,14 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo public String getDomainUser(String baseUsername, String tenantDomain) { - tenantDomain = getTenantDomain(tenantDomain); + tenantDomain = getTenantDomain(tenantDomain); return tenantService.getDomainUser(baseUsername, tenantDomain); } public String getDomain(String name) { - name = getTenantDomain(name); - return tenantService.getDomain(name); + name = getTenantDomain(name); + return tenantService.getDomain(name); } // local helpers @@ -1276,7 +1385,7 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo return getBaseNameUser(AuthenticationUtil.getAdminUserName()); } - private String getSystemUser(String tenantDomain) + protected String getSystemUser(String tenantDomain) { return tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain); } diff --git a/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java b/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java index ce03d66447..5ea9e63d52 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java +++ b/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java @@ -102,6 +102,7 @@ public class MultiTDemoTest extends TestCase private NodeService nodeService; private NodeArchiveService nodeArchiveService; + private NamespaceService namespaceService; private MutableAuthenticationService authenticationService; private PersonService personService; private SiteService siteService; @@ -175,6 +176,7 @@ public class MultiTDemoTest extends TestCase nodeService = (NodeService) ctx.getBean("NodeService"); nodeArchiveService = (NodeArchiveService) ctx.getBean("nodeArchiveService"); + namespaceService = (NamespaceService) ctx.getBean("NamespaceService"); authenticationService = (MutableAuthenticationService) ctx.getBean("AuthenticationService"); tenantAdminService = (TenantAdminService) ctx.getBean("tenantAdminService"); tenantService = (TenantService) ctx.getBean("tenantService"); @@ -200,6 +202,7 @@ public class MultiTDemoTest extends TestCase AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); // authenticate as super-admin createTenants(); + createUsers(); } @Override @@ -216,23 +219,93 @@ public class MultiTDemoTest extends TestCase } } - public synchronized void test_ALF_17681() throws Exception + private void createTenant(final String tenantDomain) + { + // create tenants (if not already created) + TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + { + public Object doWork() throws Exception + { + if (! tenantAdminService.existsTenant(tenantDomain)) + { + //tenantAdminService.createTenant(tenantDomain, DEFAULT_ADMIN_PW.toCharArray(), ROOT_DIR + "/" + tenantDomain); + tenantAdminService.createTenant(tenantDomain, (DEFAULT_ADMIN_PW+" "+tenantDomain).toCharArray(), null); // use default root dir + + logger.info("Created tenant " + tenantDomain); + } + + return null; + } + }, AuthenticationUtil.getSystemUserName()); + } + + private void deleteTenant(final String tenantDomain) + { + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + // delete tenant (if it exists) + TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + { + public Object doWork() throws Exception + { + if (tenantAdminService.existsTenant(tenantDomain)) + { + tenantAdminService.deleteTenant(tenantDomain); + + logger.info("Deleted tenant " + tenantDomain); + } + + return null; + } + }, AuthenticationUtil.getSystemUserName()); + return null; + } + }); + } + + private void createUsers() + { + for (final String tenantDomain : tenants) + { + String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); + TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + { + public Object doWork() throws Exception + { + createUser(TEST_USER1, tenantDomain, TEST_USER1+" "+tenantDomain); + createUser(TEST_USER2, tenantDomain, TEST_USER2+" "+tenantDomain); + + if (tenantDomain.equals(TEST_TENANT_DOMAIN2)) + { + createUser(TEST_USER3, tenantDomain, TEST_USER3+" "+tenantDomain); + } + + return null; + } + }, tenantAdminName); + } + } + + // note: needs to come before test10CreateCategories & test15COCIandSearch ? + public synchronized void test00_ALF_17681() throws Exception { // The issue was found on Lucene final String tenantDomain = TEST_RUN+".alf17681"; final String query = "PATH:\"/app:company_home/app:dictionary\""; - + // Create tenant createTenant(tenantDomain); - + final String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); // Search for Data dictionary by tenant admin int count = searchForDataDictionary(tenantAdminName, query); - + assertEquals("Data dictionary should be found for tenant. ", 1, count); - + indexRecoverer.setRecoveryMode(FullIndexRecoveryComponent.RecoveryMode.FULL.name()); - + // reindex Thread reindexThread = new Thread() { @@ -241,48 +314,57 @@ public class MultiTDemoTest extends TestCase indexRecoverer.reindex(); } }; - + reindexThread.start(); - + // must allow the rebuild to complete or the test after this one will fail to validate their indexes // - as they now will be deleted. reindexThread.join(); - + // wait a bit and then terminate wait(20000); indexRecoverer.setShutdown(true); wait(20000); - + // Search for Data dictionary by tenant admin int countAfter = searchForDataDictionary(tenantAdminName, query); - + assertEquals("Data dictionary should be found for tenant after FULL reindex. ", 1, countAfter); } - private int searchForDataDictionary(String tenantAdminName, final String query) + /* + public void test00_Setup() throws Throwable { - return AuthenticationUtil.runAs(new RunAsWork() - { - public Integer doWork() throws Exception - { - ResultSet resultSet = searchService.query(SPACES_STORE, SearchService.LANGUAGE_LUCENE, query, null); - return resultSet.length(); - } - }, tenantAdminName); + // test common setup (see "setUp") } + */ public void test01CreateTenants() throws Throwable - { - AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); // authenticate as super-admin + { + // ignore common setup - test here explicitly logger.info("Create tenants"); - List persons = personService.getPeople(null, null, null, new PagingRequest(0, Integer.MAX_VALUE, null)).getPage(); + final String tenantDomain1 = TEST_RUN+".one.createTenant"; + final String tenantDomain2 = TEST_RUN+".two.createTenant"; + + String[] tenantDomains = new String[] {tenantDomain1, tenantDomain2 }; + + for (final String tenantDomain : tenantDomains) + { + createTenant(tenantDomain); + } + + // check default (super-tenant) domain + + List persons = personService.getPeople(null, true, null, new PagingRequest(0, Integer.MAX_VALUE, null)).getPage(); + //assertEquals(2, personRefs.size()); // super-tenant: admin, guest (note: checking for 2 assumes that this test is run in a fresh bootstrap env) + for (PersonInfo person : persons) { String userName = person.getUserName(); - for (final String tenantDomain : tenants) + for (final String tenantDomain : tenantDomains) { assertFalse("Unexpected (tenant) user: "+userName, userName.endsWith(tenantDomain)); } @@ -290,7 +372,7 @@ public class MultiTDemoTest extends TestCase } - private void deleteTestAuthoritiesForTenant(final String[] uniqueGroupNames, final String tenantName) + private void deleteTestAuthoritiesForTenant(final String[] uniqueGroupNames, final String userName) { // Check deletion for tenant1 TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() @@ -298,7 +380,7 @@ public class MultiTDemoTest extends TestCase public Object doWork() throws Exception { // find person - NodeRef personNodeRef = personService.getPerson(tenantName); + NodeRef personNodeRef = personService.getPerson(userName); NodeRef homeSpaceRef = (NodeRef)nodeService.getProperty(personNodeRef, ContentModel.PROP_HOMEFOLDER); assertNotNull(homeSpaceRef); @@ -309,9 +391,9 @@ public class MultiTDemoTest extends TestCase } return null; } - }, tenantName); + }, userName); } - private void createTestAuthoritiesForTenant(final String[] uniqueGroupNames, final String tenantName) + private void createTestAuthoritiesForTenant(final String[] uniqueGroupNames, final String userName) { // Create groups for tenant TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() @@ -319,7 +401,7 @@ public class MultiTDemoTest extends TestCase public Object doWork() throws Exception { // find person - NodeRef personNodeRef = personService.getPerson(tenantName); + NodeRef personNodeRef = personService.getPerson(userName); NodeRef homeSpaceRef = (NodeRef)nodeService.getProperty(personNodeRef, ContentModel.PROP_HOMEFOLDER); assertNotNull(homeSpaceRef); @@ -331,17 +413,17 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantName); + }, userName); } - private void checkTestAuthoritiesPresence(final String[] uniqueGroupNames, final String tenantName, final boolean shouldPresent) + private void checkTestAuthoritiesPresence(final String[] uniqueGroupNames, final String userName, final boolean shouldPresent) { // Check that created permissions are not visible to tenant 2 TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() { public Object doWork() throws Exception { - NodeRef personNodeRef = personService.getPerson(tenantName); + NodeRef personNodeRef = personService.getPerson(userName); NodeRef homeSpaceRef = (NodeRef)nodeService.getProperty(personNodeRef, ContentModel.PROP_HOMEFOLDER); Set perms = permissionService.getAllSetPermissions(homeSpaceRef); Set auths = authorityService.getAllAuthorities(AuthorityType.GROUP); @@ -363,12 +445,314 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantName); + }, userName); } - // TODO fix CLOUD-1348 - public void xtest02NonSharedGroupDeletion() + private void createGroup(String shortName, String parentShortName) { + // create new Group using authority Service + String groupName = this.authorityService.getName(AuthorityType.GROUP, shortName); + if (this.authorityService.authorityExists(groupName) == false) + { + String parentGroupName = null; + if (parentShortName != null) + { + parentGroupName = this.authorityService.getName(AuthorityType.GROUP, parentShortName); + if (this.authorityService.authorityExists(parentGroupName) == false) + { + logger.warn("Parent group does not exist: " + parentShortName); + return; + } + } + + this.authorityService.createAuthority(AuthorityType.GROUP, shortName); + + if (parentGroupName != null) + { + addToGroup(parentShortName, groupName); + } + } + else + { + logger.warn("Group already exists: " + shortName); + } + } + + private void addToGroup(String parentGroupShortName, String authorityName) + { + String parentGroupName = this.authorityService.getName(AuthorityType.GROUP, parentGroupShortName); + authorityService.addAuthority(parentGroupName, authorityName); + } + + private NodeRef createUser(String baseUserName, String tenantDomain, String password) + { + String userName = tenantService.getDomainUser(baseUserName, tenantDomain); + + NodeRef personNodeRef = null; + + if (! this.authenticationService.authenticationExists(userName)) + { + NodeRef baseHomeFolder = getUserHomesNodeRef(SPACES_STORE); + + // Create the users home folder + NodeRef homeFolder = createHomeSpaceFolderNode( + baseHomeFolder, + baseUserName, + userName); + + // Create the authentication + this.authenticationService.createAuthentication(userName, password.toCharArray()); + + // Create the person + Map personProperties = new HashMap(); + personProperties.put(ContentModel.PROP_USERNAME, userName); + personProperties.put(ContentModel.PROP_HOMEFOLDER, homeFolder); + personProperties.put(ContentModel.PROP_FIRSTNAME, baseUserName); + personProperties.put(ContentModel.PROP_LASTNAME, baseUserName+"-"+tenantDomain); // add domain suffix here for demo only + personProperties.put(ContentModel.PROP_EMAIL, userName); + + personNodeRef = this.personService.createPerson(personProperties); + + // ensure the user can access their own Person object + this.permissionService.setPermission(personNodeRef, userName, permissionService.getAllPermission(), true); + + NodeRef checkHomeSpaceRef = (NodeRef)nodeService.getProperty(personNodeRef, ContentModel.PROP_HOMEFOLDER); + assertNotNull(checkHomeSpaceRef); + + logger.info("Created user " + userName); + } + else + { + personNodeRef = personService.getPerson(userName); + + logger.info("Found existing user " + userName); + } + + return personNodeRef; + } + + private void loginLogoutUser(String username, String password) + { + // authenticate via the authentication service + authenticationService.authenticate(username, password.toCharArray()); + + // set the user name as stored by the back end + username = authenticationService.getCurrentUserName(); + + NodeRef personRef = personService.getPerson(username); + NodeRef homeSpaceRef = (NodeRef)nodeService.getProperty(personRef, ContentModel.PROP_HOMEFOLDER); + + // check that the home space node exists - else user cannot login + if (nodeService.exists(homeSpaceRef) == false) + { + throw new InvalidNodeRefException(homeSpaceRef); + } + + // logout + authenticationService.clearCurrentSecurityContext(); + } + + private NodeRef getUserHomesNodeRef(StoreRef storeRef) + { + // get the "User Homes" location + return findFolderNodeRef(storeRef, "/app:company_home/app:user_homes"); + } + + private NodeRef getWebClientExtensionNodeRef(StoreRef storeRef) + { + // get the "Web Client Extensions" location + return findFolderNodeRef(storeRef, "/app:company_home/app:dictionary/app:webclient_extension"); + } + + private NodeRef findFolderNodeRef(StoreRef storeRef, String folderXPath) + { + NodeRef storeRootNodeRef = nodeService.getRootNode(storeRef); + + List nodeRefs = searchService.selectNodes(storeRootNodeRef, folderXPath, null, namespaceService, false); + + NodeRef folderNodeRef = null; + if (nodeRefs.size() != 1) + { + throw new AlfrescoRuntimeException("Cannot find folder location: " + folderXPath); + } + else + { + folderNodeRef = nodeRefs.get(0); + } + return folderNodeRef; + } + + private NodeRef createFolderNode(NodeRef parentFolderNodeRef, String nameValue) + { + if (nameValue != null) + { + Map folderProps = new HashMap(); + folderProps.put(ContentModel.PROP_NAME, nameValue); + + return this.nodeService.createNode( + parentFolderNodeRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, nameValue), + ContentModel.TYPE_FOLDER, + folderProps).getChildRef(); + } + + return null; + } + + private NodeRef createCategory(StoreRef storeRef, NodeRef parentCategoryRef, String name, String description) + { + // create category using categoryservice + NodeRef ref; + if (parentCategoryRef == null) + { + ref = this.categoryService.createRootCategory(storeRef, ContentModel.ASPECT_GEN_CLASSIFIABLE, name); + } + else + { + ref = categoryService.createCategory(parentCategoryRef, name); + } + + // apply the titled aspect - for description + Map titledProps = new HashMap(1, 1.0f); + titledProps.put(ContentModel.PROP_DESCRIPTION, description); + this.nodeService.addAspect(ref, ContentModel.ASPECT_TITLED, titledProps); + + return ref; + } + + private NodeRef createHomeSpaceFolderNode(NodeRef folderNodeRef, String spaceName, String userName) + { + if (spaceName != null) + { + Map folderProps = new HashMap(); + folderProps.put(ContentModel.PROP_NAME, spaceName); + + NodeRef nodeRef = this.nodeService.createNode( + folderNodeRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, spaceName), + ContentModel.TYPE_FOLDER, + folderProps).getChildRef(); + + // apply the uifacets aspect - icon and title props + Map uiFacetsProps = new HashMap(3); + uiFacetsProps.put(ApplicationModel.PROP_ICON, "space-icon-default"); + uiFacetsProps.put(ContentModel.PROP_TITLE, spaceName); + this.nodeService.addAspect(nodeRef, ApplicationModel.ASPECT_UIFACETS, uiFacetsProps); + + setupHomeSpacePermissions(nodeRef, userName); + + return nodeRef; + } + + return null; + } + + private void setupHomeSpacePermissions(NodeRef homeSpaceRef, String userName) + { + // Admin Authority has full permissions by default (automatic - set in the permission config) + // give full permissions to the new user + this.permissionService.setPermission(homeSpaceRef, userName, permissionService.getAllPermission(), true); + + // by default other users will only have GUEST access to the space contents + String permission = "Consumer"; + + if (permission != null && permission.length() != 0) + { + this.permissionService.setPermission(homeSpaceRef, permissionService.getAllAuthorities(), permission, true); + } + + // the new user is the OWNER of their own space and always has full permissions + this.ownableService.setOwner(homeSpaceRef, userName); + this.permissionService.setPermission(homeSpaceRef, permissionService.getOwnerAuthority(), permissionService.getAllPermission(), true); + + // now detach (if we did this first we could not set any permissions!) + this.permissionService.setInheritParentPermissions(homeSpaceRef, false); + } + + private NodeRef getHomeSpaceFolderNode(String userName) + { + return (NodeRef)this.nodeService.getProperty(personService.getPerson(userName), ContentModel.PROP_HOMEFOLDER); + } + + private NodeRef addContent(NodeRef spaceRef, String name, String textData, String mimeType) + { + Map contentProps = new HashMap(); + contentProps.put(ContentModel.PROP_NAME, name); + + ChildAssociationRef association = nodeService.createNode(spaceRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, name), + ContentModel.TYPE_CONTENT, + contentProps); + + NodeRef content = association.getChildRef(); + + // add titled aspect (for Web Client display) + Map titledProps = new HashMap(); + titledProps.put(ContentModel.PROP_TITLE, name); + titledProps.put(ContentModel.PROP_DESCRIPTION, name); + this.nodeService.addAspect(content, ContentModel.ASPECT_TITLED, titledProps); + + ContentWriter writer = contentService.getWriter(content, ContentModel.PROP_CONTENT, true); + + writer.setMimetype(mimeType); + writer.setEncoding("UTF-8"); + + writer.putContent(textData); + + return content; + } + + private NodeRef addContent(NodeRef spaceRef, String name, InputStream is, String mimeType) + { + Map contentProps = new HashMap(); + contentProps.put(ContentModel.PROP_NAME, name); + + ChildAssociationRef association = nodeService.createNode(spaceRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, name), + ContentModel.TYPE_CONTENT, + contentProps); + + NodeRef content = association.getChildRef(); + + // add titled aspect (for Web Client display) + Map titledProps = new HashMap(); + titledProps.put(ContentModel.PROP_TITLE, name); + titledProps.put(ContentModel.PROP_DESCRIPTION, name); + this.nodeService.addAspect(content, ContentModel.ASPECT_TITLED, titledProps); + + ContentWriter writer = contentService.getWriter(content, ContentModel.PROP_CONTENT, true); + + writer.setMimetype(mimeType); + writer.setEncoding("UTF-8"); + + writer.putContent(is); + + return content; + } + + private SiteInfo createSite(String siteId) + { + SiteInfo siteInfo = siteService.createSite(null, siteId, "title - "+siteId, "description - "+siteId, SiteVisibility.PRIVATE); + + // ensure that the Document Library folder is pre-created so that test code can start creating content straight away. + // At the time of writing V4.1 does not create this folder automatically, but Thor does. + NodeRef result = siteService.getContainer(siteId, SiteService.DOCUMENT_LIBRARY); + if (result == null) + { + result = siteService.createContainer(siteId, SiteService.DOCUMENT_LIBRARY, ContentModel.TYPE_FOLDER, null); + } + + return siteInfo; + } + + public void test02NonSharedGroupDeletion() + { + // ignore common setup - test here explicitly + final String tenantDomain1 = TEST_RUN+".groupdel1"; final String tenantDomain2 = TEST_RUN+".groupdel2"; @@ -434,9 +818,10 @@ public class MultiTDemoTest extends TestCase } } - // TODO fix CLOUD-1348 - public void xtest03SharedGroupDeletion() + public void test03SharedGroupDeletion() { + // ignore common setup - test here explicitly + final String tenantDomain1 = TEST_RUN+".groupdel3"; final String tenantDomain2 = TEST_RUN+".groupdel4"; @@ -526,54 +911,11 @@ public class MultiTDemoTest extends TestCase } - private void createTenant(final String tenantDomain) - { - // create tenants (if not already created) - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() - { - public Object doWork() throws Exception - { - if (! tenantAdminService.existsTenant(tenantDomain)) - { - //tenantAdminService.createTenant(tenantDomain, DEFAULT_ADMIN_PW.toCharArray(), ROOT_DIR + "/" + tenantDomain); - tenantAdminService.createTenant(tenantDomain, (DEFAULT_ADMIN_PW+" "+tenantDomain).toCharArray(), null); // use default root dir - - logger.info("Created tenant " + tenantDomain); - } - - return null; - } - }, AuthenticationUtil.getSystemUserName()); - } - - private void deleteTenant(final String tenantDomain) - { - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() - { - public Object execute() throws Throwable - { - // delete tenant (if it exists) - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() - { - public Object doWork() throws Exception - { - if (tenantAdminService.existsTenant(tenantDomain)) - { - tenantAdminService.deleteTenant(tenantDomain); - - logger.info("Deleted tenant " + tenantDomain); - } - - return null; - } - }, AuthenticationUtil.getSystemUserName()); - return null; - } - }); - } public void test04_ETHREEOH_2015() { + // ignore common setup - test here explicitly + final String tenantDomain1 = TEST_RUN+".one.ethreeoh2015"; final String tenantDomain2 = TEST_RUN+".two.ethreeoh2015"; @@ -607,44 +949,32 @@ public class MultiTDemoTest extends TestCase } - public void test05CreateUsers() throws Throwable + public void test05ValidateUsers() throws Throwable { - logger.info("Create demo users"); + // use common setup + logger.info("Validate demo users"); - List persons = personService.getPeople(null, null, null, new PagingRequest(0, Integer.MAX_VALUE, null)).getPage(); - //assertEquals(2, personRefs.size()); // super-tenant: admin, guest (note: checking for 2 assumes that this test is run in a fresh bootstrap env) - for (PersonInfo person : persons) + AuthenticationUtil.runAs(new RunAsWork() { - String userName = person.getUserName(); - for (final String tenantDomain : tenants) + public Object doWork() throws Exception { - assertFalse("Unexpected (tenant) user: "+userName, userName.endsWith(tenantDomain)); + List persons = personService.getPeople(null, true, null, new PagingRequest(0, Integer.MAX_VALUE, null)).getPage(); + //assertEquals(2, personRefs.size()); // super-tenant: admin, guest (note: checking for 2 assumes that this test is run in a fresh bootstrap env) + for (PersonInfo person : persons) + { + String userName = person.getUserName(); + for (final String tenantDomain : tenants) + { + assertFalse("Unexpected (tenant) user: "+userName, userName.endsWith(tenantDomain)); + } + } + + return null; } - } + }, AuthenticationUtil.getAdminUserName()); try { - for (final String tenantDomain : tenants) - { - String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); - - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() - { - public Object doWork() throws Exception - { - createUser(TEST_USER1, tenantDomain, TEST_USER1+" "+tenantDomain); - createUser(TEST_USER2, tenantDomain, TEST_USER2+" "+tenantDomain); - - if (tenantDomain.equals(TEST_TENANT_DOMAIN2)) - { - createUser(TEST_USER3, tenantDomain, TEST_USER3+" "+tenantDomain); - } - - return null; - } - }, tenantAdminName); - } - for (final String tenantDomain : tenants) { String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); @@ -662,7 +992,7 @@ public class MultiTDemoTest extends TestCase String userName = (String)nodeService.getProperty(personRef, ContentModel.PROP_USERNAME); assertTrue(userName.endsWith(tenantDomain)); - logger.info("Create users: get all people - found user: "+userName); + logger.info("Validate users: get all people - found user: "+userName); NodeRef homeSpaceRef = (NodeRef)nodeService.getProperty(personRef, ContentModel.PROP_HOMEFOLDER); assertNotNull(homeSpaceRef); @@ -693,6 +1023,7 @@ public class MultiTDemoTest extends TestCase public void test06LoginTenantUsers() throws Throwable { + // use common setup logger.info("Login tenant users"); try @@ -1369,23 +1700,9 @@ public class MultiTDemoTest extends TestCase } } - // TODO fix CLOUD-1348 - // pseudo cleanup - if this test runs last - public void xtest20DeleteAllTenants() - { - logger.info("test delete tenants"); - - AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); - - List allTenants = tenantAdminService.getAllTenants(); - for (final Tenant tenant : allTenants) - { - deleteTenant(tenant.getTenantDomain()); - } - } // TODO pending CLOUD-1350 fix - public void xtest21_ALF_12732() + public void xtest20_ALF_12732() { final String tenantDomain1 = TEST_RUN+".one.alf12732"; @@ -1425,7 +1742,7 @@ public class MultiTDemoTest extends TestCase } } - public void test22_ALF_14354() + public void test21_ALF_14354() { final String tenantDomain1 = TEST_RUN+".one.alf14354"; final String tenantDomain2 = TEST_RUN+".two.alf14354"; @@ -1460,304 +1777,31 @@ public class MultiTDemoTest extends TestCase }, tenantAdminName); } - private void createGroup(String shortName, String parentShortName) + + private int searchForDataDictionary(String tenantAdminName, final String query) { - // create new Group using authority Service - String groupName = this.authorityService.getName(AuthorityType.GROUP, shortName); - if (this.authorityService.authorityExists(groupName) == false) + return AuthenticationUtil.runAs(new RunAsWork() { - String parentGroupName = null; - if (parentShortName != null) - { - parentGroupName = this.authorityService.getName(AuthorityType.GROUP, parentShortName); - if (this.authorityService.authorityExists(parentGroupName) == false) - { - logger.warn("Parent group does not exist: " + parentShortName); - return; - } - } - - this.authorityService.createAuthority(AuthorityType.GROUP, shortName); - - if (parentGroupName != null) - { - addToGroup(parentShortName, groupName); - } + public Integer doWork() throws Exception + { + ResultSet resultSet = searchService.query(SPACES_STORE, SearchService.LANGUAGE_LUCENE, query, null); + return resultSet.length(); + } + }, tenantAdminName); + } + + // pseudo cleanup - if this test runs last + public void test22DeleteAllTenants() + { + logger.info("test delete tenants"); + + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + + List allTenants = tenantAdminService.getAllTenants(); + for (final Tenant tenant : allTenants) + { + deleteTenant(tenant.getTenantDomain()); } - else - { - logger.warn("Group already exists: " + shortName); - } - } - - private void addToGroup(String parentGroupShortName, String authorityName) - { - String parentGroupName = this.authorityService.getName(AuthorityType.GROUP, parentGroupShortName); - authorityService.addAuthority(parentGroupName, authorityName); - } - - private NodeRef createUser(String baseUserName, String tenantDomain, String password) - { - String userName = tenantService.getDomainUser(baseUserName, tenantDomain); - - NodeRef personNodeRef = null; - - if (! this.authenticationService.authenticationExists(userName)) - { - NodeRef baseHomeFolder = getUserHomesNodeRef(SPACES_STORE); - - // Create the users home folder - NodeRef homeFolder = createHomeSpaceFolderNode( - baseHomeFolder, - baseUserName, - userName); - - // Create the authentication - this.authenticationService.createAuthentication(userName, password.toCharArray()); - - // Create the person - Map personProperties = new HashMap(); - personProperties.put(ContentModel.PROP_USERNAME, userName); - personProperties.put(ContentModel.PROP_HOMEFOLDER, homeFolder); - personProperties.put(ContentModel.PROP_FIRSTNAME, baseUserName); - personProperties.put(ContentModel.PROP_LASTNAME, baseUserName+"-"+tenantDomain); // add domain suffix here for demo only - personProperties.put(ContentModel.PROP_EMAIL, userName); - - personNodeRef = this.personService.createPerson(personProperties); - - // ensure the user can access their own Person object - this.permissionService.setPermission(personNodeRef, userName, permissionService.getAllPermission(), true); - - NodeRef checkHomeSpaceRef = (NodeRef)nodeService.getProperty(personNodeRef, ContentModel.PROP_HOMEFOLDER); - assertNotNull(checkHomeSpaceRef); - - logger.info("Created user " + userName); - } - else - { - personNodeRef = personService.getPerson(userName); - - logger.info("Found existing user " + userName); - } - - return personNodeRef; - } - - private void loginLogoutUser(String username, String password) - { - // authenticate via the authentication service - authenticationService.authenticate(username, password.toCharArray()); - - // set the user name as stored by the back end - username = authenticationService.getCurrentUserName(); - - NodeRef personRef = personService.getPerson(username); - NodeRef homeSpaceRef = (NodeRef)nodeService.getProperty(personRef, ContentModel.PROP_HOMEFOLDER); - - // check that the home space node exists - else user cannot login - if (nodeService.exists(homeSpaceRef) == false) - { - throw new InvalidNodeRefException(homeSpaceRef); - } - - // logout - authenticationService.clearCurrentSecurityContext(); - } - - private NodeRef getUserHomesNodeRef(StoreRef storeRef) - { - // get the "User Homes" location - return findFolderNodeRef(storeRef, "/app:company_home/app:user_homes"); - } - - private NodeRef getWebClientExtensionNodeRef(StoreRef storeRef) - { - // get the "Web Client Extensions" location - return findFolderNodeRef(storeRef, "/app:company_home/app:dictionary/app:webclient_extension"); - } - - private NodeRef findFolderNodeRef(StoreRef storeRef, String folderXPath) - { - ResultSet rs = this.searchService.query(storeRef, SearchService.LANGUAGE_XPATH, folderXPath); - - NodeRef folderNodeRef = null; - if (rs.length() != 1) - { - throw new AlfrescoRuntimeException("Cannot find folder location: " + folderXPath); - } - else - { - folderNodeRef = rs.getNodeRef(0); - } - rs.close(); - return folderNodeRef; - } - - private NodeRef createFolderNode(NodeRef parentFolderNodeRef, String nameValue) - { - if (nameValue != null) - { - Map folderProps = new HashMap(); - folderProps.put(ContentModel.PROP_NAME, nameValue); - - return this.nodeService.createNode( - parentFolderNodeRef, - ContentModel.ASSOC_CONTAINS, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, nameValue), - ContentModel.TYPE_FOLDER, - folderProps).getChildRef(); - } - - return null; - } - - private NodeRef createCategory(StoreRef storeRef, NodeRef parentCategoryRef, String name, String description) - { - // create category using categoryservice - NodeRef ref; - if (parentCategoryRef == null) - { - ref = this.categoryService.createRootCategory(storeRef, ContentModel.ASPECT_GEN_CLASSIFIABLE, name); - } - else - { - ref = categoryService.createCategory(parentCategoryRef, name); - } - - // apply the titled aspect - for description - Map titledProps = new HashMap(1, 1.0f); - titledProps.put(ContentModel.PROP_DESCRIPTION, description); - this.nodeService.addAspect(ref, ContentModel.ASPECT_TITLED, titledProps); - - return ref; - } - - private NodeRef createHomeSpaceFolderNode(NodeRef folderNodeRef, String spaceName, String userName) - { - if (spaceName != null) - { - Map folderProps = new HashMap(); - folderProps.put(ContentModel.PROP_NAME, spaceName); - - NodeRef nodeRef = this.nodeService.createNode( - folderNodeRef, - ContentModel.ASSOC_CONTAINS, - QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, spaceName), - ContentModel.TYPE_FOLDER, - folderProps).getChildRef(); - - // apply the uifacets aspect - icon and title props - Map uiFacetsProps = new HashMap(3); - uiFacetsProps.put(ApplicationModel.PROP_ICON, "space-icon-default"); - uiFacetsProps.put(ContentModel.PROP_TITLE, spaceName); - this.nodeService.addAspect(nodeRef, ApplicationModel.ASPECT_UIFACETS, uiFacetsProps); - - setupHomeSpacePermissions(nodeRef, userName); - - return nodeRef; - } - - return null; - } - - private void setupHomeSpacePermissions(NodeRef homeSpaceRef, String userName) - { - // Admin Authority has full permissions by default (automatic - set in the permission config) - // give full permissions to the new user - this.permissionService.setPermission(homeSpaceRef, userName, permissionService.getAllPermission(), true); - - // by default other users will only have GUEST access to the space contents - String permission = "Consumer"; - - if (permission != null && permission.length() != 0) - { - this.permissionService.setPermission(homeSpaceRef, permissionService.getAllAuthorities(), permission, true); - } - - // the new user is the OWNER of their own space and always has full permissions - this.ownableService.setOwner(homeSpaceRef, userName); - this.permissionService.setPermission(homeSpaceRef, permissionService.getOwnerAuthority(), permissionService.getAllPermission(), true); - - // now detach (if we did this first we could not set any permissions!) - this.permissionService.setInheritParentPermissions(homeSpaceRef, false); - } - - private NodeRef getHomeSpaceFolderNode(String userName) - { - return (NodeRef)this.nodeService.getProperty(personService.getPerson(userName), ContentModel.PROP_HOMEFOLDER); - } - - private NodeRef addContent(NodeRef spaceRef, String name, String textData, String mimeType) - { - Map contentProps = new HashMap(); - contentProps.put(ContentModel.PROP_NAME, name); - - ChildAssociationRef association = nodeService.createNode(spaceRef, - ContentModel.ASSOC_CONTAINS, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, name), - ContentModel.TYPE_CONTENT, - contentProps); - - NodeRef content = association.getChildRef(); - - // add titled aspect (for Web Client display) - Map titledProps = new HashMap(); - titledProps.put(ContentModel.PROP_TITLE, name); - titledProps.put(ContentModel.PROP_DESCRIPTION, name); - this.nodeService.addAspect(content, ContentModel.ASPECT_TITLED, titledProps); - - ContentWriter writer = contentService.getWriter(content, ContentModel.PROP_CONTENT, true); - - writer.setMimetype(mimeType); - writer.setEncoding("UTF-8"); - - writer.putContent(textData); - - return content; - } - - private NodeRef addContent(NodeRef spaceRef, String name, InputStream is, String mimeType) - { - Map contentProps = new HashMap(); - contentProps.put(ContentModel.PROP_NAME, name); - - ChildAssociationRef association = nodeService.createNode(spaceRef, - ContentModel.ASSOC_CONTAINS, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, name), - ContentModel.TYPE_CONTENT, - contentProps); - - NodeRef content = association.getChildRef(); - - // add titled aspect (for Web Client display) - Map titledProps = new HashMap(); - titledProps.put(ContentModel.PROP_TITLE, name); - titledProps.put(ContentModel.PROP_DESCRIPTION, name); - this.nodeService.addAspect(content, ContentModel.ASPECT_TITLED, titledProps); - - ContentWriter writer = contentService.getWriter(content, ContentModel.PROP_CONTENT, true); - - writer.setMimetype(mimeType); - writer.setEncoding("UTF-8"); - - writer.putContent(is); - - return content; - } - - private SiteInfo createSite(String siteId) - { - SiteInfo siteInfo = siteService.createSite(null, siteId, "title - "+siteId, "description - "+siteId, SiteVisibility.PRIVATE); - - // ensure that the Document Library folder is pre-created so that test code can start creating content straight away. - // At the time of writing V4.1 does not create this folder automatically, but Thor does. - NodeRef result = siteService.getContainer(siteId, SiteService.DOCUMENT_LIBRARY); - if (result == null) - { - result = siteService.createContainer(siteId, SiteService.DOCUMENT_LIBRARY, ContentModel.TYPE_FOLDER, null); - } - - return siteInfo; } /* diff --git a/source/java/org/alfresco/repo/tenant/MultiTNodeServiceInterceptor.java b/source/java/org/alfresco/repo/tenant/MultiTNodeServiceInterceptor.java index 2ff7d3f31e..dcf192cb97 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTNodeServiceInterceptor.java +++ b/source/java/org/alfresco/repo/tenant/MultiTNodeServiceInterceptor.java @@ -106,9 +106,9 @@ public class MultiTNodeServiceInterceptor extends DelegatingIntroductionIntercep String methodName = invocation.getMethod().getName(); - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { - logger.debug("Intercepting method " + methodName); + logger.trace("Intercepting method " + methodName); } Object[] args = invocation.getArguments(); @@ -118,11 +118,11 @@ public class MultiTNodeServiceInterceptor extends DelegatingIntroductionIntercep Object arg = args[i]; Object newArg = convertInboundValue(arg); - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { if (!EqualsHelper.nullSafeEquals(newArg, arg)) { - logger.debug( + logger.trace( "Argument converted: \n" + " Before: " + arg + "\n" + " After: " + newArg); diff --git a/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java b/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java index 0868af2b44..640540ae6c 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java +++ b/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java @@ -712,7 +712,7 @@ public class MultiTServiceImpl implements TenantService Tenant tenant = null; if (tenantEntity != null) { - tenant = new Tenant(tenantEntity.getTenantDomain(), tenantEntity.getEnabled(), tenantEntity.getContentRoot()); + tenant = new Tenant(tenantEntity.getTenantDomain(), tenantEntity.getEnabled(), tenantEntity.getContentRoot(), null); } return tenant; } diff --git a/source/java/org/alfresco/repo/tenant/RunAsTenantInterceptor.java b/source/java/org/alfresco/repo/tenant/RunAsTenantInterceptor.java new file mode 100644 index 0000000000..612d520fb0 --- /dev/null +++ b/source/java/org/alfresco/repo/tenant/RunAsTenantInterceptor.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2005-2013 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.tenant; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +/** + * @since 4.2 + */ +public class RunAsTenantInterceptor implements MethodInterceptor +{ + public enum TENANT_TYPE + { + Default, + RealUser + } + + private TENANT_TYPE tenantType; + + public RunAsTenantInterceptor(TENANT_TYPE tenantType) + { + this.tenantType = tenantType; + } + + @Override + public Object invoke(final MethodInvocation mi) throws Throwable + { + TenantRunAsWork runAs = new TenantRunAsWork() + { + public Object doWork() throws Exception + { + try + { + return mi.proceed(); + } + catch(Throwable e) + { + e.printStackTrace(); + + // Re-throw the exception + if (e instanceof RuntimeException) + { + throw (RuntimeException) e; + } + throw new RuntimeException("Failed to execute in RunAsTenant context", e); + } + } + }; + + if (tenantType == TENANT_TYPE.Default) + { + return TenantUtil.runAsDefaultTenant(runAs); + } + else + { + return TenantUtil.runAsPrimaryTenant(runAs, AuthenticationUtil.getFullyAuthenticatedUser()); + } + } +} diff --git a/source/java/org/alfresco/repo/tenant/SingleTAdminServiceImpl.java b/source/java/org/alfresco/repo/tenant/SingleTAdminServiceImpl.java index 079fdddf09..20c9ee22ae 100644 --- a/source/java/org/alfresco/repo/tenant/SingleTAdminServiceImpl.java +++ b/source/java/org/alfresco/repo/tenant/SingleTAdminServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -146,7 +146,12 @@ public class SingleTAdminServiceImpl implements TenantAdminService /** * @throws UnsupportedOperationException always */ - public void createTenant(String tenantDomain, char[] adminRawPassword, String rootContentStoreDir) + public void createTenant(String tenantDomain, char[] adminRawPassword, String contentRoot) + { + throw new UnsupportedOperationException("Single tenant mode is active."); + } + + public void createTenant(String tenantDomain, char[] adminRawPassword, String contentRoot, String dbUrl) { throw new UnsupportedOperationException("Single tenant mode is active."); } diff --git a/source/java/org/alfresco/repo/tenant/TenantAdminService.java b/source/java/org/alfresco/repo/tenant/TenantAdminService.java index 0e1ef25bb2..f4f6d5df6d 100644 --- a/source/java/org/alfresco/repo/tenant/TenantAdminService.java +++ b/source/java/org/alfresco/repo/tenant/TenantAdminService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -63,12 +63,15 @@ public interface TenantAdminService extends TenantUserService */ public void createTenant(String tenantDomain, char[] adminRawPassword); + + public void createTenant(String tenantDomain, char[] adminRawPassword, String contentRoot); - public void createTenant(String tenantDomain, char[] adminRawPassword, String rootContentStoreDir); + // experimental (unsupported) + public void createTenant(String tenantDomain, char[] adminRawPassword, String contentRoot, String dbUrl); public void exportTenant(String tenantDomain, File directoryDestination); - public void importTenant(String tenantDomain, File directorySource, String rootContentStoreDir); + public void importTenant(String tenantDomain, File directorySource, String contentRoot); public boolean existsTenant(String tenantDomain); diff --git a/source/java/org/alfresco/repo/tenant/TenantBasicDataSource.java b/source/java/org/alfresco/repo/tenant/TenantBasicDataSource.java new file mode 100644 index 0000000000..f62adb445a --- /dev/null +++ b/source/java/org/alfresco/repo/tenant/TenantBasicDataSource.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2005-2013 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.tenant; + +import java.sql.SQLException; + +import org.apache.commons.dbcp.BasicDataSource; + +/** + * Experimental + * + * @author janv + * @since 4.2 + */ +public class TenantBasicDataSource extends BasicDataSource +{ + public TenantBasicDataSource(BasicDataSource bds, String tenantUrl, int tenantMaxActive) throws SQLException + { + // tenant-specific + this.setUrl(tenantUrl); + this.setMaxActive(tenantMaxActive == -1 ? bds.getMaxActive() : tenantMaxActive); + + // defaults + this.setUsername(bds.getUsername()); + this.setPassword(bds.getPassword()); + this.setDriverClassName(bds.getDriverClassName()); + + this.setMaxIdle(bds.getMaxIdle()); + this.setMinIdle(bds.getMinIdle()); + + // TODO other default settings + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/tenant/TenantInterpreter.java b/source/java/org/alfresco/repo/tenant/TenantInterpreter.java index 63d5bf89c5..bd8691f932 100644 --- a/source/java/org/alfresco/repo/tenant/TenantInterpreter.java +++ b/source/java/org/alfresco/repo/tenant/TenantInterpreter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -28,6 +28,7 @@ import java.util.List; import org.alfresco.repo.admin.BaseInterpreter; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.MutableAuthenticationService; @@ -142,7 +143,12 @@ public class TenantInterpreter extends BaseInterpreter implements ApplicationCon return executeCommand(line); } }; - return transactionService.getRetryingTransactionHelper().doInTransaction(txnWork); + + // from Thor + RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); + txnHelper.setMaxRetries(1); + + return txnHelper.doInTransaction(txnWork); } }; return AuthenticationUtil.runAs(executeWork, AuthenticationUtil.SYSTEM_USER_NAME); @@ -224,8 +230,8 @@ public class TenantInterpreter extends BaseInterpreter implements ApplicationCon { if (tenant.isEnabled()) { - String rootContentStoreDir = tenant.getRootContentStoreDir(); - out.println("Enabled - Tenant: " + tenant.getTenantDomain() + " (" + rootContentStoreDir + ")"); + String contentRoot = tenant.getRootContentStoreDir(); + out.println("Enabled - Tenant: " + tenant.getTenantDomain() + " (" + contentRoot + ")"); } } @@ -235,8 +241,8 @@ public class TenantInterpreter extends BaseInterpreter implements ApplicationCon { if (! tenant.isEnabled()) { - String rootContentStoreDir = tenant.getRootContentStoreDir(); - out.println("Disabled - Tenant: " + tenant.getTenantDomain() + " (" + rootContentStoreDir + ")"); + String contentRoot = tenant.getRootContentStoreDir(); + out.println("Disabled - Tenant: " + tenant.getTenantDomain() + " (" + contentRoot + ")"); } } } @@ -251,14 +257,14 @@ public class TenantInterpreter extends BaseInterpreter implements ApplicationCon String tenantDomain = new String(command[2]).toLowerCase(); Tenant tenant = tenantAdminService.getTenant(tenantDomain); - String rootContentStoreDir = tenant.getRootContentStoreDir(); + String contentRoot = tenant.getRootContentStoreDir(); if (tenant.isEnabled()) { - out.println("Enabled - Tenant: " + tenant.getTenantDomain() + " (" + rootContentStoreDir + ")"); + out.println("Enabled - Tenant: " + tenant.getTenantDomain() + " (" + contentRoot + ")"); } else { - out.println("Disabled - Tenant: " + tenant.getTenantDomain() + " (" + rootContentStoreDir + ")"); + out.println("Disabled - Tenant: " + tenant.getTenantDomain() + " (" + contentRoot + ")"); } } @@ -270,21 +276,36 @@ public class TenantInterpreter extends BaseInterpreter implements ApplicationCon else if (command[0].equals("create")) { - if ((command.length != 3) && (command.length != 4)) + if ((command.length < 3) || (command.length > 5)) { return "Syntax Error, try 'help'.\n"; } String newTenant = new String(command[1]).toLowerCase(); char[] tenantAdminRawPassword = new String(command[2]).toCharArray(); - String rootContentStoreDir = null; - if (command.length == 4) + String contentRoot = null; + if (command.length >= 4) { - rootContentStoreDir = new String(command[3]); + contentRoot = new String(command[3]); + if ("null".equals(contentRoot)) + { + contentRoot = null; + } } - tenantAdminService.createTenant(newTenant, tenantAdminRawPassword, rootContentStoreDir); + String dbUrl = null; + if (command.length >= 5) + { + // experimental (unsupported) + dbUrl = new String(command[4]); + if ("null".equals(dbUrl)) + { + dbUrl = null; + } + } + tenantAdminService.createTenant(newTenant, tenantAdminRawPassword, contentRoot, dbUrl); + out.println("created tenant: " + newTenant); } @@ -298,13 +319,13 @@ public class TenantInterpreter extends BaseInterpreter implements ApplicationCon String newTenant = new String(command[1]).toLowerCase(); File directorySource = new File(command[2]); - String rootContentStoreDir = null; + String contentRoot = null; if (command.length == 4) { - rootContentStoreDir = new String(command[3]); + contentRoot = new String(command[3]); } - tenantAdminService.importTenant(newTenant, directorySource, rootContentStoreDir); + tenantAdminService.importTenant(newTenant, directorySource, contentRoot); out.println("imported tenant: " + newTenant); } diff --git a/source/java/org/alfresco/repo/tenant/TenantRoutingContentStore.java b/source/java/org/alfresco/repo/tenant/TenantRoutingContentStore.java index ef6d6f1ee2..cc866f9e43 100644 --- a/source/java/org/alfresco/repo/tenant/TenantRoutingContentStore.java +++ b/source/java/org/alfresco/repo/tenant/TenantRoutingContentStore.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -24,4 +24,5 @@ package org.alfresco.repo.tenant; */ public interface TenantRoutingContentStore extends TenantDeployer { + public String getRootLocation(); } diff --git a/source/java/org/alfresco/repo/tenant/TenantRoutingDataSource.java b/source/java/org/alfresco/repo/tenant/TenantRoutingDataSource.java new file mode 100644 index 0000000000..d8bd8b3656 --- /dev/null +++ b/source/java/org/alfresco/repo/tenant/TenantRoutingDataSource.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2005-2013 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.tenant; + +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +import javax.sql.DataSource; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.apache.commons.dbcp.BasicDataSource; +import org.springframework.extensions.surf.util.ParameterCheck; +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; + +/** + * Experimental + * + * @author janv + * @since 4.2 + */ +public class TenantRoutingDataSource extends AbstractRoutingDataSource +{ + Map tenantDataSources = new HashMap(); + + private BasicDataSource baseDataSource; + + private TenantService tenantService; + + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + public void setBaseDataSource(BasicDataSource baseDataSource) + { + this.baseDataSource = baseDataSource; + } + + @Override + protected Object determineCurrentLookupKey() + { + //return tenantService.getCurrentUserDomain(); // note: this is re-entrant if it checks whether tenant is enabled ! + String runAsUser = AuthenticationUtil.getRunAsUser(); + String tenantDomain = TenantService.DEFAULT_DOMAIN; + if (runAsUser != null) + { + String[] parts = runAsUser.split(TenantService.SEPARATOR); + if (parts.length == 2) + { + tenantDomain = parts[1]; + } + } + return tenantDomain; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public void afterPropertiesSet() + { + ParameterCheck.mandatory("baseDataSource", baseDataSource); + ParameterCheck.mandatory("tenantDataSources", tenantDataSources); + + String dbUrl = baseDataSource.getUrl(); + setTargetDataSources((Map)tenantDataSources); + + try + { + // default tenant + DataSource defaultTargetDataSource = new TenantBasicDataSource(baseDataSource, dbUrl, -1); + tenantDataSources.put(TenantService.DEFAULT_DOMAIN, defaultTargetDataSource); + setDefaultTargetDataSource(defaultTargetDataSource); + } + catch (SQLException se) + { + throw new RuntimeException(se); + } + + + + super.afterPropertiesSet(); + } + + public synchronized void addTenantDataSource(String tenantDomain, String dbUrl) throws SQLException + { + String currentTenantDomain = tenantService.getCurrentUserDomain(); + if (! TenantService.DEFAULT_DOMAIN.equals(currentTenantDomain)) + { + throw new RuntimeException("Unexpected - should not be in context of a tenant ["+currentTenantDomain+"]"); + } + + tenantDataSources.put(tenantDomain, new TenantBasicDataSource(baseDataSource, dbUrl, -1)); + + super.afterPropertiesSet(); // to update resolved data sources + } + + public synchronized void removeTenantDataSource(String tenantDomain) throws SQLException + { + String currentTenantDomain = tenantService.getCurrentUserDomain(); + if (! TenantService.DEFAULT_DOMAIN.equals(currentTenantDomain)) + { + throw new RuntimeException("Unexpected - should not be in context of a tenant ["+currentTenantDomain+"]"); + } + + tenantDataSources.remove(tenantDomain); + + super.afterPropertiesSet(); // to update resolved data sources + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException + { + return null; + } +} + diff --git a/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java b/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java index 3a0769ca0c..5cf9274989 100644 --- a/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java +++ b/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -29,13 +29,19 @@ import java.util.concurrent.locks.ReentrantLock; import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.usage.UsageDAO; import org.alfresco.repo.domain.usage.UsageDAO.MapHandler; +import org.alfresco.repo.node.NodeServicePolicies; +import org.alfresco.repo.node.NodeServicePolicies.OnCreateNodePolicy; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.tenant.Tenant; import org.alfresco.repo.tenant.TenantAdminService; import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.TransactionServiceImpl; +import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; @@ -47,12 +53,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationEvent; import org.springframework.extensions.surf.util.AbstractLifecycleBean; -import org.alfresco.repo.node.NodeServicePolicies; -import org.alfresco.repo.node.NodeServicePolicies.OnCreateNodePolicy; -import org.alfresco.repo.policy.JavaBehaviour; -import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.service.cmr.repository.ChildAssociationRef; - /** * User Usage Tracking Component - to allow user usages to be collapsed or re-calculated * @@ -187,16 +187,16 @@ public class UserUsageTrackingComponent extends AbstractLifecycleBean implements List tenants = tenantAdminService.getAllTenants(); for (Tenant tenant : tenants) { - AuthenticationUtil.runAs(new RunAsWork() + TenantUtil.runAsSystemTenant(new TenantRunAsWork() { public Object doWork() throws Exception { bootstrapInternal(); return null; } - }, tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenant.getTenantDomain())); + }, tenant.getTenantDomain()); } - } + } } public void bootstrapInternal() @@ -310,7 +310,10 @@ public class UserUsageTrackingComponent extends AbstractLifecycleBean implements String username = (String)result.get("username"); String uuid = (String)result.get("uuid"); - users.put(username, new NodeRef(personStoreRef, uuid)); + if (! username.equalsIgnoreCase(AuthenticationUtil.getSystemUserName())) + { + users.put(username, new NodeRef(personStoreRef, uuid)); + } } }; usageDAO.getUsersWithUsage(tenantService.getName(personStoreRef), userHandler); @@ -406,7 +409,10 @@ public class UserUsageTrackingComponent extends AbstractLifecycleBean implements String username = (String)result.get("username"); String uuid = (String)result.get("uuid"); - users.put(username, new NodeRef(personStoreRef, uuid)); + if (! username.equalsIgnoreCase(AuthenticationUtil.getSystemUserName())) + { + users.put(username, new NodeRef(personStoreRef, uuid)); + } } }; @@ -592,13 +598,13 @@ public class UserUsageTrackingComponent extends AbstractLifecycleBean implements int collapseCount = 0; for (final NodeRef usageNodeRef : usageNodeRefs) { - Boolean collapsed = AuthenticationUtil.runAs(new RunAsWork() + Boolean collapsed = TenantUtil.runAsSystemTenant(new TenantRunAsWork() { public Boolean doWork() throws Exception { return collapseUsage(usageNodeRef); } - }, tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantService.getDomain(usageNodeRef.getStoreRef().getIdentifier()))); + }, tenantService.getDomain(usageNodeRef.getStoreRef().getIdentifier())); if (collapsed) { diff --git a/source/java/org/alfresco/repo/version/MigrationCleanupJob.java b/source/java/org/alfresco/repo/version/MigrationCleanupJob.java index aebf4ad448..cdc9cc24f0 100644 --- a/source/java/org/alfresco/repo/version/MigrationCleanupJob.java +++ b/source/java/org/alfresco/repo/version/MigrationCleanupJob.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -22,9 +22,10 @@ import java.util.List; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.tenant.Tenant; import org.alfresco.repo.tenant.TenantAdminService; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.quartz.Job; @@ -167,15 +168,14 @@ public class MigrationCleanupJob implements Job List tenants = tenantAdminService.getAllTenants(); for (Tenant tenant : tenants) { - String tenantDomain = tenant.getTenantDomain(); - AuthenticationUtil.runAs(new RunAsWork() + TenantUtil.runAsSystemTenant(new TenantRunAsWork() { public Object doWork() throws Exception { migrationCleanup.executeCleanup(batchSize, threadCount); return null; } - }, tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); + }, tenant.getTenantDomain()); } } } diff --git a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java index d90f1eca0e..aa033fb7f3 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -426,7 +426,7 @@ public class WorkflowServiceImpl implements WorkflowService String engineId = BPMEngineRegistry.getEngineId(workflowDefinitionId); WorkflowComponent component = getWorkflowComponent(engineId); WorkflowPath path = component.startWorkflow(workflowDefinitionId, parameters); - if(parameters!=null && parameters.containsKey(WorkflowModel.ASSOC_PACKAGE)) + if(path != null && parameters!=null && parameters.containsKey(WorkflowModel.ASSOC_PACKAGE)) { WorkflowInstance instance = path.getInstance(); workflowPackageComponent.setWorkflowForPackage(instance); diff --git a/source/java/org/alfresco/repo/workflow/activiti/script/ActivitiScriptBase.java b/source/java/org/alfresco/repo/workflow/activiti/script/ActivitiScriptBase.java index edda0c72cd..510b60062e 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/script/ActivitiScriptBase.java +++ b/source/java/org/alfresco/repo/workflow/activiti/script/ActivitiScriptBase.java @@ -168,6 +168,7 @@ public class ActivitiScriptBase { userName = AuthenticationUtil.getFullyAuthenticatedUser(); } + // The "System" user is a special case, which has no person object associated with it. if(userName != null && !AuthenticationUtil.SYSTEM_USER_NAME.equals(userName)) { diff --git a/source/java/org/alfresco/service/cmr/module/ModuleService.java b/source/java/org/alfresco/service/cmr/module/ModuleService.java index b5e222c976..582cd15650 100644 --- a/source/java/org/alfresco/service/cmr/module/ModuleService.java +++ b/source/java/org/alfresco/service/cmr/module/ModuleService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -69,4 +69,6 @@ public interface ModuleService * transaction. */ void startModules(); + + void shutdownModules(); } diff --git a/source/java/org/alfresco/service/cmr/security/PersonService.java b/source/java/org/alfresco/service/cmr/security/PersonService.java index 5ac684c364..4715b3ec0d 100644 --- a/source/java/org/alfresco/service/cmr/security/PersonService.java +++ b/source/java/org/alfresco/service/cmr/security/PersonService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -233,6 +233,16 @@ public interface PersonService @Auditable(parameters = {"personRef"}) public void deletePerson(NodeRef personRef); + /** + * Delete the person identified by the given ref, and optionally delete + * the associated authentication, if one. + * + * @param personRef + * @param deleteAuthentication + */ + @Auditable(parameters = {"personRef", "deleteAuthentication"}) + public void deletePerson(NodeRef personRef, boolean deleteAuthentication); + /** * Get all the people we know about. *