From 65b8cd4a8bdac12e932491a0a81c57ddf55de2db Mon Sep 17 00:00:00 2001 From: Derek Hulley Date: Fri, 13 Mar 2009 01:52:39 +0000 Subject: [PATCH] Merged V3.1 to HEAD 13424: ETHREEOH-1242: Sample LDAP authentication config breaks site invites in Share 13427: Fixes for ETHREEOH-1157: Propagate exceptions using ReportedException 13428: Fix ETHREEOH-1493: Upgrade from 2.1-A to 3.1 uses incorrect patch id and fixes_to_schema 13429: Specific fix for ETHREEOH-1157: duplicate/triplicate users not properly prohibited 13436: Merged V2.2 to V3.1 13435: Merged V2.1 to V2.2 12307: Merged DEV/V2.1SP7 to 2.1 11927: ETWOONE-396 12112: ETWOONE-396 13437: Fixed ETHREEOH-1498: Mismatched closing XML tag in ehcache-custom.xml.sample.cluster 13439: Fix for ETHREEOH-1157: JSF Dialogs Absorbing Exceptions 13456: Fixed ETHREEOH-1472: Changes to systemBootstrap cause bootstrapping ACP's not to work 13469: Upgrade patch to update internal version2Store counter (follow-on fix for ETHREEOH-1540) 13491: Chaining example for DOC-84 13492: Fixed paths in zip file 13494: Fixed GenericBootstrapPatch when overriding bootstrap views 13495: Added @version javadoc 13496: Minor logging updates 13497: Fixed ETHREEOH-1431: Authentication case sensitivity switch doesn't work 13500: Temporary fix for Sharepoint issue raised last week 13502: ETHREEOH-1575: It's impossible to create Change Request task 13511: Fix for ETHREEOH-1549: Impossible to create HTML web content 13529: Fix for ETHREEOH-1595 13531: Fix for ETHREEOH-1607: Error on chaining example xml - malformed comment 13537: Build fix ... exclude the system user from auto creation 13538: Build Fix - further contraints to aviod auto-creation of guest ___________________________________________________________________ Modified: svn:mergeinfo Merged /alfresco/BRANCHES/V2.1:r12307 Merged /alfresco/BRANCHES/V2.2:r13435 Merged /alfresco/BRANCHES/V3.1:r 13424,13427-13429,13436-13437,13439,13442-13450,13452,13454-13456, 13469-13473,13475-13476,13479-13480,13491-13500,13502,13511,13529-13538 git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@13619 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/bootstrap-context.xml | 191 +- .../AlfrescoSchemaUpdate-Person.sql | 8 +- ...lfrescoSchemaUpdate-2.1-A--to--2.2-ACL.sql | 2 +- .../alfresco/extension/chaining-example.zip | Bin 0 -> 6062 bytes .../ehcache-custom.xml.sample.cluster | 2 +- .../jaas-authentication-context.xml.sample | 11 +- .../ldap-authentication-context.xml.sample | 17 +- .../extension/mt/mt-admin-context.xml.sample | 11 + config/alfresco/import-export-context.xml | 178 ++ .../messages/patch-service.properties | 7 +- .../alfresco/patch/patch-services-context.xml | 33 +- config/alfresco/version.properties | 2 +- .../patch/impl/GenericBootstrapPatch.java | 5 +- ...MigrateVersionStoreUpdateCounterPatch.java | 63 + .../repo/avm/AVMExpiredContentProcessor.java | 2 +- .../repo/domain/hibernate/Permission.hbm.xml | 8 +- .../AbstractAuthenticationComponent.java | 81 +- .../authentication/AuthenticationTest.java | 2 +- .../repo/security/person/PersonDaoImpl.java | 36 +- .../security/person/PersonServiceImpl.java | 7 +- .../repo/security/person/UserNameMatcher.java | 8 +- .../repo/tenant/MultiTAdminServiceImpl.java | 2508 ++++++++--------- .../repo/version/Version2ServiceImpl.java | 38 +- .../repo/version/VersionMigrator.java | 2 +- .../repo/version/VersionMigratorTest.java | 108 +- .../version/common/VersionHistoryImpl.java | 36 +- .../tenant/mt-admin-context.xml | 11 + 27 files changed, 1829 insertions(+), 1548 deletions(-) create mode 100644 config/alfresco/extension/chaining-example.zip create mode 100644 source/java/org/alfresco/repo/admin/patch/impl/MigrateVersionStoreUpdateCounterPatch.java diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index b0b15b578d..bdef462051 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -82,7 +82,7 @@ - + @@ -175,183 +175,18 @@ - - - - - - - - - / - alfresco/bootstrap/alfrescoUserStore.xml - - - /${alfresco_user_store.system_container.childname} - alfresco/bootstrap/alfrescoAuthorityStore.xml - - - /${alfresco_user_store.system_container.childname} - alfresco/bootstrap/alfrescoAuthorityStorePermission.xml - - - /${alfresco_user_store.system_container.childname}/sys:authorities - alfresco/bootstrap/emailServer.xml - - - /${alfresco_user_store.system_container.childname}/sys:authorities - alfresco/bootstrap/adminGroup.xml - - - - - - - - - - / - alfresco/bootstrap/descriptor.xml - - - / - alfresco/bootstrap/systemRegistry.xml - - - - - - - - - - / - alfresco/bootstrap/lightWeightVersionStore.xml - - - - - - - - - - / - alfresco/bootstrap/version2Store.xml - - - - - - - - - - / - alfresco/bootstrap/spacesArchive.xml - - - - - - - - - - - - - - / - alfresco/bootstrap/spaces.xml - alfresco/messages/bootstrap-spaces - - - / - alfresco/bootstrap/system.xml - - - / - alfresco/bootstrap/categories.xml - - - / - alfresco/bootstrap/multilingualRoot.xml - - - /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.childname} - alfresco/templates/software_engineering_project.xml - alfresco/messages/bootstrap-templates - - - /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.content.childname} - alfresco/templates/content_template_examples.xml - alfresco/messages/bootstrap-content-template-examples - - - /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.content.childname} - alfresco/templates/readme_template.xml - - - /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname} - alfresco/templates/email_templates.acp - alfresco/messages/bootstrap-content-template-examples - - - /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.rss.childname} - alfresco/templates/rss_templates.acp - alfresco/messages/bootstrap-content-template-examples - - - /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.scripts.childname} - alfresco/bootstrap/example_javascripts.acp - - - /${spaces.company_home.childname}/${spaces.dictionary.childname} - alfresco/bootstrap/webScripts.xml - alfresco/messages/bootstrap-webScripts - - - /${spaces.company_home.childname}/${spaces.dictionary.childname} - alfresco/bootstrap/webScriptsExtensions.xml - alfresco/messages/bootstrap-webScriptsExtensions - - - /${spaces.company_home.childname}/${spaces.dictionary.childname} - alfresco/bootstrap/webScriptsReadme.xml - - - - /${spaces.company_home.childname}/${spaces.dictionary.childname} - alfresco/bootstrap/customModelsSpace.acp - - - /${spaces.company_home.childname}/${spaces.dictionary.childname} - alfresco/bootstrap/customMessagesSpace.xml - - - /${spaces.company_home.childname}/${spaces.dictionary.childname} - alfresco/bootstrap/customWebClientExtensionSpace.xml - - - - /${spaces.company_home.childname}/${spaces.dictionary.childname} - alfresco/bootstrap/customWorkflowDefsSpace.acp - - - - /cm:categoryRoot - alfresco/bootstrap/tagRootCategory.xml - - - - /${spaces.company_home.childname} - alfresco/bootstrap/sitesSpace.xml - alfresco/messages/bootstrap-spaces - - - - + + + + + + + + + + diff --git a/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Dialect/AlfrescoSchemaUpdate-Person.sql b/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Dialect/AlfrescoSchemaUpdate-Person.sql index 15494ee959..7553e11a53 100644 --- a/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Dialect/AlfrescoSchemaUpdate-Person.sql +++ b/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.Dialect/AlfrescoSchemaUpdate-Person.sql @@ -22,7 +22,7 @@ UPDATE qname_localname = ( SELECT - p.string_value + LOWER(p.string_value) FROM alf_node_properties p JOIN alf_qname q on p.qname_id = q.id @@ -55,11 +55,11 @@ UPDATE -- -- Record script finish -- -DELETE FROM alf_applied_patch WHERE id = 'patch.db-V2.2-Person'; +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V2.2-Person-2'; INSERT INTO alf_applied_patch (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) VALUES ( - 'patch.db-V2.2-Person', 'Manually executed script upgrade V2.2: Person user name also in the association qname', - 0, 90, -1, 91, null, 'UNKOWN', 1, 1, 'Script completed' + 'patch.db-V2.2-Person-2', 'Manually executed script upgrade V2.2: Person user name also in the association qname', + 0, 1007, -1, 1008, null, 'UNKOWN', 1, 1, 'Script completed' ); diff --git a/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoSchemaUpdate-2.1-A--to--2.2-ACL.sql b/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoSchemaUpdate-2.1-A--to--2.2-ACL.sql index 65553f9f48..8ab860eae1 100644 --- a/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoSchemaUpdate-2.1-A--to--2.2-ACL.sql +++ b/config/alfresco/dbscripts/upgrade/2.2/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoSchemaUpdate-2.1-A--to--2.2-ACL.sql @@ -181,7 +181,7 @@ ALTER TABLE alf_access_control_entry -- -- Record script finish -- -DELETE FROM alf_applied_patch WHERE id = 'patch.db-V2.2-ACL'; +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V2.2-ACL-From-2.1-A'; INSERT INTO alf_applied_patch (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) VALUES diff --git a/config/alfresco/extension/chaining-example.zip b/config/alfresco/extension/chaining-example.zip new file mode 100644 index 0000000000000000000000000000000000000000..1bf199b78d132b2aee02d7398f42d114d0d84c42 GIT binary patch literal 6062 zcmb7|WmFvNmWCT|+}$-mBaMUv3l=;`aDt@+H0}-!1a}GU8azPb79_X^cb5PG5(pZ6 zICss=;huBv+%>Cy?5ZENp0&SvYs;&uh=fc8Km-5*mTpiXX}zM)nuq|v5C#Ci0RRAu z%^?=H7Pe-b5NoKTsgtoCC&b0s+|<_D!WiOgVQ0%}Y-j6i>h8?tZev}Sq-3`U#tJ?} zI;Yhzf>qZo2Y-=Yhke2vgG$HKxK5d{kXR}PGxZI%({3^u984L3wxz`RDlcPe`$j!*9Vi^0U!~3Za>x{^t--A2_Q2_v&2YFhXKA|Ap#7JPzo5Fi^2wTitG=e*n zhzSQQTYoJx=vyFp>wNdKtB?8!Q`@D~Ki7HJuw1a1-GjP;``zH>baF)cx9<&?*VJUz z0|On}aPI;y0Y;2g2a+hIcl85@5Ua8VUq3No&enJOT{v1;H4Z+-)ECFUNs-xqtGt^F zBnUC=@#~Z>Zlv8#>ss*$2-2pQBydTV-4rNhA$Ql)MeZMqoIKCdp=b|>CqF_J&9vW z@#aNEqtJ9KfYTP;DU50Iaa2M!71+DC@K5DGMAwe@zSRV^`EF;CfUs?OjwnP&vep8# zK+^VT^)M9h{sy60vzHR4?b)24(VyZa@PrD%WT3?lg%&^y1OQkc*H&9+Yn#8Q_@C;k z#%sF;FrE)|;w~ULSp?!{H;Jr44*`bp@~=T+DxAbM`l4aVe0R4QW3GN&Y;2|9&=#Ao z+fJ6fpEcgbD;xt+3Cn};v^_ToRVDkv7O-zeu4M1ieTA=XERLA3$SZugpKQ@IN>(pL z?$pHo_U_dhGOFoGs4}Pi@QXqo(h8sgk`{;uY;~!ez1Pt$DMT+8mYgK>+?w{UP;LE|`65vb&DWuQSE**m!?My4>=NN%PBejkfpuL<=uPnY(@=JNu zW!F_kQtnKW?W8ce*NJ2nvB{=beffqFss3`gy1F7xV8JmA{I*XKJ3=&|4(}~Wq|4R{ zA;-b)iO-Wp;e0LAgRuA7Unu9aCPW*AEixk96Qb8`lA1N$aIBVqYk8jxqe^FmbBoDa z6Xy0`)eRxo&>_?Li1&cLCAJ#uadlY8yE2cYzDl3EOlSbrlJl?ES?o^7+J66#&JHYd6HBop1Rcp#j9a;wRxtt z)byDG{$;7@#vtyKR@vM(>VOiqQHMf$h)%k0ToVWSzLW}2Zx8~nq}JWDHeOig#X$Nx z-1BVohrSN#>Lo*RM;L25dkOkqe|wMe=;le>#w00W;phY zZsHSAFfm3Ng}eMOQ`AQ56clITWWVpCDzxIJ0u_3w1LjQh2l#~1xvADX>3%N#_TR|8 z7GC8lB+35*99kBAw#mLFY|9mH&=X*h&eF_F}V(n-c z%hOTGrLEX-wq7~rATHRcO_DtOX}kcIrj#rM7G$2aL6FP~wMk`e5ZJN3iykULNZ&&? zMV1~&KLrulZ8pf9g2JPGmrlGix`*ss_-0rU3xuK1T^F^v6Le+Vd;NOOhR_lp#et9TQb>0j%YyrjTUnN5HNy5d~)UGEMUXkiXN`;;M;QG`QVhJGIr>}z>N$8b;g--%CTfK zcMxNK){l>T`-)g>XM3ni1$i~P-Jjp2IGSH*MY?6){62^`j$K>(ss^NSJ?;b?ip~#) z`%E4}T}3r;&V%fpz1+4IOD-Ny#@E=mGOYUA6x8;K!H|$SW6hC+l@Q$(24q`QHC+h*>&9>F)i?s6^=;%nlIP!FwUn%k z`}ngP09Q$XMtEOhp3rjJ*@hVP`cma;F(cX~%xRY54~Blqc}=%RDyW&?a4tuhKl;Y> z4Ylca)*gUl_%z6ZKh-sb_v)ixrlM$i?#!v1k}4f~*tx5USWGRW?B?#+a_D|FC4rc}-0cfxW%9pcU z1x<(KIm2&g19_}6dK#j+t7REx2v??eODcs-+0oo!L-qMEzY`K_O=J~^X47vglM&F({G7=Phb zlGJHlx0^-=H>yh#c&6N7MOkh(yu7z{!Jw;Njm9eb+MCy4^QS^a@#bzm{k&8q`6k?G5Lw==MSJ{J~JMUbG9VUKY^<)3c^g=ukTLw{ZV0He)T(ik2r3sud!+LVcLzvr?IG|*r+R@qsOZOj#>A1Yr` z?7vD^LR!UWcKD`|zLnQ3T)pOL-jLNSEpXF*5?aD^J-orBjkro%JNW4-E#Gj*%Xvn^j4;)cHdc zN=Z4U(>}5=m#YrXXsN z?8@SC;Bo#CgXl;`+2Ha(km9gSJ16o;rcQ1|k3f*9ARQss=I^<~m~00Z{M_!?hg3<4 z_N8ZYRppRDJ`|x3Iui6%!5=k-59~>??+Z0HDOjG5PDpSUS64(3D;1;m`6`kTE0E+2 z(ykDr^N)VTr>Cd4I>Q}|FvSO>Kao!9*Av0xGjK5;2jL$@G>z=c0a+w*p zdkT@&XjAZPjAraN9~hCc*yRgZBb%2HWz5MKvP57Zak1UYks)$v_~zid!+R)}L5_fi z1j4p7hnlad*=-Alw6*u#lfmrKEA0p~mS8d4o*KOR2OJ&0UPrH=sd0oj$qh$x20}pxc?j?S6)_V2xNhr+O z9<1T~jmZtMI6-|>4EYonFT_VzN4Cl!Q;hvGa$<1OHo;Ac`9T}3kav=ujS#wcWbPpOlE42*vtsO zBMp9X!CT6O0&Z=Wp&8bSMOVTGBk2VvFO=$&>%<-PU=O__RgY$~DQ!(C&_QXknKpMx zdV_`SI!1MleiAyjKBf_dmSosZvG-!Smr3W`6ck#fA!jTLn#b!cXV@o1bwIZUjutO- z{XDF8A^a(Fo?)9v5X4X3=LEkIB%XLn%Yd6RFmBjl>S^^u>4~F<>y^APt8ujiJx~O# zt_7L%hweNrXDAUtr5mpRN*Zyr|A_jjc3FoVtI_&P?SW==V5~k!~dEG%@4?11x zxjRw~Dzx9!qK%gCGClh}x~5|X@er$Z8a6Ilay7{66l&n<0uf+1E(oUs;|bOR-CSQb zxSPp3M?BozXSpet`u!4Hc2Zf~%U+ehUFW27vPb=&Z8_5dJ5E zA;_PkAD$o6|L#*gre{BNpMSBbJ#wuc^YF~(VphBc5OUtLAjRbV86WfhZY@0K>7zW# z_CFX6kIAaz1mU)SWcJ??Z%U&)#Py#u{rfuln4I^}WB=Da{ue1fUQQpA#Yx1`|H$?I z9a2%`Lp;J}7S{U98UMYPKPIo@JR<*x&i`6|1mTNB=RNnk5Ahy_>b5jKZqZe`Nu({JZ$*@0P4dv{vet@qCcMg3n6v9 AYXATM literal 0 HcmV?d00001 diff --git a/config/alfresco/extension/ehcache-custom.xml.sample.cluster b/config/alfresco/extension/ehcache-custom.xml.sample.cluster index 2619851903..517c46c777 100644 --- a/config/alfresco/extension/ehcache-custom.xml.sample.cluster +++ b/config/alfresco/extension/ehcache-custom.xml.sample.cluster @@ -10,7 +10,7 @@ properties="heartbeatInterval=5000, peerDiscovery=automatic, multicastGroupAddress=230.0.0.1, - multicastGroupPort=4446"/> + multicastGroupPort=4446" /> - - - true - - + + + + + + \ No newline at end of file diff --git a/config/alfresco/extension/ldap-authentication-context.xml.sample b/config/alfresco/extension/ldap-authentication-context.xml.sample index d024f42463..225141e2f4 100644 --- a/config/alfresco/extension/ldap-authentication-context.xml.sample +++ b/config/alfresco/extension/ldap-authentication-context.xml.sample @@ -14,17 +14,14 @@ - - - - - true - - - true - - + + + + + + + diff --git a/config/alfresco/extension/mt/mt-admin-context.xml.sample b/config/alfresco/extension/mt/mt-admin-context.xml.sample index 4836306b9b..5d63e0f970 100644 --- a/config/alfresco/extension/mt/mt-admin-context.xml.sample +++ b/config/alfresco/extension/mt/mt-admin-context.xml.sample @@ -3,6 +3,17 @@ + + + + + + + + diff --git a/config/alfresco/import-export-context.xml b/config/alfresco/import-export-context.xml index 66e4987505..1fcdf4f55b 100644 --- a/config/alfresco/import-export-context.xml +++ b/config/alfresco/import-export-context.xml @@ -341,5 +341,183 @@ ${version.store.version2Store} + + + + + + + + + / + alfresco/bootstrap/alfrescoUserStore.xml + + + /${alfresco_user_store.system_container.childname} + alfresco/bootstrap/alfrescoAuthorityStore.xml + + + /${alfresco_user_store.system_container.childname} + alfresco/bootstrap/alfrescoAuthorityStorePermission.xml + + + /${alfresco_user_store.system_container.childname}/sys:authorities + alfresco/bootstrap/emailServer.xml + + + /${alfresco_user_store.system_container.childname}/sys:authorities + alfresco/bootstrap/adminGroup.xml + + + + + + + + + + / + alfresco/bootstrap/descriptor.xml + + + / + alfresco/bootstrap/systemRegistry.xml + + + + + + + + + + / + alfresco/bootstrap/lightWeightVersionStore.xml + + + + + + + + + + / + alfresco/bootstrap/version2Store.xml + + + + + + + + + + / + alfresco/bootstrap/spacesArchive.xml + + + + + + + + + + / + alfresco/bootstrap/spaces.xml + alfresco/messages/bootstrap-spaces + + + / + alfresco/bootstrap/system.xml + + + / + alfresco/bootstrap/categories.xml + + + / + alfresco/bootstrap/multilingualRoot.xml + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.childname} + alfresco/templates/software_engineering_project.xml + alfresco/messages/bootstrap-templates + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.content.childname} + alfresco/templates/content_template_examples.xml + alfresco/messages/bootstrap-content-template-examples + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.content.childname} + alfresco/templates/readme_template.xml + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname} + alfresco/templates/email_templates.acp + alfresco/messages/bootstrap-content-template-examples + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.rss.childname} + alfresco/templates/rss_templates.acp + alfresco/messages/bootstrap-content-template-examples + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.scripts.childname} + alfresco/bootstrap/example_javascripts.acp + + + /${spaces.company_home.childname}/${spaces.dictionary.childname} + alfresco/bootstrap/webScripts.xml + alfresco/messages/bootstrap-webScripts + + + /${spaces.company_home.childname}/${spaces.dictionary.childname} + alfresco/bootstrap/webScriptsExtensions.xml + alfresco/messages/bootstrap-webScriptsExtensions + + + /${spaces.company_home.childname}/${spaces.dictionary.childname} + alfresco/bootstrap/webScriptsReadme.xml + + + + /${spaces.company_home.childname}/${spaces.dictionary.childname} + alfresco/bootstrap/customModelsSpace.acp + + + /${spaces.company_home.childname}/${spaces.dictionary.childname} + alfresco/bootstrap/customMessagesSpace.xml + + + /${spaces.company_home.childname}/${spaces.dictionary.childname} + alfresco/bootstrap/customWebClientExtensionSpace.xml + + + + /${spaces.company_home.childname}/${spaces.dictionary.childname} + alfresco/bootstrap/customWorkflowDefsSpace.acp + + + + /cm:categoryRoot + alfresco/bootstrap/tagRootCategory.xml + + + + /${spaces.company_home.childname} + alfresco/bootstrap/sitesSpace.xml + alfresco/messages/bootstrap-spaces + + + + diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties index 3d03b80c84..ff8b841cbe 100644 --- a/config/alfresco/messages/patch-service.properties +++ b/config/alfresco/messages/patch-service.properties @@ -182,8 +182,8 @@ patch.AVMProperties.result=Properties were moved. patch.customModels.description=Adds 'Models' folder to Data Dictionary patch.customMessages.description=Adds 'Messages' folder to Data Dictionary -patch.customWebClientExtension.description=Adds 'Web Client Extension' folder to Data Dictionary - +patch.customWebClientExtension.description=Adds 'Web Client Extension' folder to Data Dictionary + patch.customWorkflowDefs.description=Adds 'Workflow Definitions' folder to Data Dictionary. patch.emailContributorGroup.description=Adds the 'GROUP_EMAIL_CONTRIBUTORS' group. @@ -245,3 +245,6 @@ patch.administratorGroup.description=Adds the 'ALFRESCO_ADMINISTRATORS' group. patch.moveWCMToGroupBasedPermissionsPatch.description=Move WCM to group based permissions. patch.moveWCMToGroupBasedPermissionsPatch.result=WCM moved to group based permissions. + +patch.migrateVersionStoreUpdateCounter.description=Update internal version2Store counter (if needed). +patch.migrateVersionStoreUpdateCounter.result=Update internal version2Store counter (if needed): {0} diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 6cf0c40975..989aba0526 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -1531,17 +1531,6 @@ - - - patch.db-V2.2-Person - patch.schemaUpgradeScript.description - 0 - 134 - 135 - - classpath:alfresco/dbscripts/upgrade/2.2/${db.script.dialect}/AlfrescoSchemaUpdate-Person.sql - - patch.redeploySubmitProcess4 @@ -1716,4 +1705,26 @@ + + patch.migrateVersionStoreUpdateCounter + patch.migrateVersionStoreUpdateCounter.description + 0 + 2004 + 2005 + + + + + + + + patch.db-V2.2-Person-2 + patch.schemaUpgradeScript.description + 0 + 2005 + 2006 + + classpath:alfresco/dbscripts/upgrade/2.2/${db.script.dialect}/AlfrescoSchemaUpdate-Person.sql + + diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index e5dc1ccebe..5a7b486753 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=2004 +version.schema=2006 diff --git a/source/java/org/alfresco/repo/admin/patch/impl/GenericBootstrapPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/GenericBootstrapPatch.java index 7387d7572b..567ab98368 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/GenericBootstrapPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/GenericBootstrapPatch.java @@ -24,7 +24,7 @@ */ package org.alfresco.repo.admin.patch.impl; -import java.util.Collections; +import java.util.ArrayList; import java.util.List; import java.util.Properties; @@ -118,7 +118,8 @@ public class GenericBootstrapPatch extends AbstractPatch } } String path = bootstrapView.getProperty("path"); - List bootstrapViews = Collections.singletonList(bootstrapView); + List bootstrapViews = new ArrayList(1); + bootstrapViews.add(bootstrapView); // modify the bootstrapper importerBootstrap.setBootstrapViews(bootstrapViews); importerBootstrap.setUseExistingStore(true); // allow import into existing store diff --git a/source/java/org/alfresco/repo/admin/patch/impl/MigrateVersionStoreUpdateCounterPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/MigrateVersionStoreUpdateCounterPatch.java new file mode 100644 index 0000000000..e8ff181e64 --- /dev/null +++ b/source/java/org/alfresco/repo/admin/patch/impl/MigrateVersionStoreUpdateCounterPatch.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing + */ +package org.alfresco.repo.admin.patch.impl; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.repo.version.Version2Model; +import org.alfresco.repo.version.VersionModel; +import org.alfresco.repo.version.common.counter.VersionCounterService; +import org.alfresco.service.cmr.repository.StoreRef; + +/** + * Update internal version2Store counter if needed (eg. affects upgrades from 2.x to 3.0.1, will not affect upgrades from 2.x to 3.1.0) + */ +public class MigrateVersionStoreUpdateCounterPatch extends AbstractPatch +{ + private static final String MSG_SUCCESS = "patch.migrateVersionStoreUpdateCounter.result"; + + private VersionCounterService versionCounterService; + + public void setVersionCounterService(VersionCounterService versionCounterService) + { + this.versionCounterService = versionCounterService; + } + + @Override + protected String applyInternal() throws Exception + { + int oldV1count = versionCounterService.currentVersionNumber(new StoreRef(StoreRef.PROTOCOL_WORKSPACE, VersionModel.STORE_ID)); + int oldV2count = versionCounterService.currentVersionNumber(new StoreRef(StoreRef.PROTOCOL_WORKSPACE, Version2Model.STORE_ID)); + + int newV2count = (oldV1count+oldV2count); + versionCounterService.setVersionNumber(new StoreRef(StoreRef.PROTOCOL_WORKSPACE, Version2Model.STORE_ID), newV2count); + + // build the result message + String msg = I18NUtil.getMessage(MSG_SUCCESS, "oldV1count="+oldV1count+",oldV2count="+oldV2count+",newV2count="+newV2count); + + // done + return msg; + } +} diff --git a/source/java/org/alfresco/repo/avm/AVMExpiredContentProcessor.java b/source/java/org/alfresco/repo/avm/AVMExpiredContentProcessor.java index 6b61d5e540..625f874a95 100644 --- a/source/java/org/alfresco/repo/avm/AVMExpiredContentProcessor.java +++ b/source/java/org/alfresco/repo/avm/AVMExpiredContentProcessor.java @@ -200,7 +200,7 @@ public class AVMExpiredContentProcessor }; // perform the work as the system user - AuthenticationUtil.runAs(authorisedWork, AuthenticationUtil.getAdminRoleName()); + AuthenticationUtil.runAs(authorisedWork, AuthenticationUtil.getAdminUserName()); // now we know everything worked ok, let the virtualisation server // know about all the new workflow sandboxes created (just the main stores) diff --git a/source/java/org/alfresco/repo/domain/hibernate/Permission.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/Permission.hbm.xml index ced4516b58..19d5f679aa 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/Permission.hbm.xml +++ b/source/java/org/alfresco/repo/domain/hibernate/Permission.hbm.xml @@ -461,7 +461,8 @@ ]]> - + + SELECT {n.*} @@ -471,15 +472,14 @@ JOIN alf_child_assoc c on c.child_node_id = n.id JOIN alf_store s on s.id = n.store_id WHERE - c.qname_localname = :userName1 AND + c.qname_localname = :userNameLowerCase AND p.qname_id = :qnamePropId AND - p.string_value = :userName2 AND n.type_qname_id = :qnameTypeId AND n.node_deleted = :False AND s.protocol = :storeProtocol AND s.identifier = :storeIdentifier - + SELECT diff --git a/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java b/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java index 262e523117..596521e91d 100644 --- a/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java +++ b/source/java/org/alfresco/repo/security/authentication/AbstractAuthenticationComponent.java @@ -40,6 +40,7 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.transaction.TransactionService; @@ -56,7 +57,7 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC * The abstract class keeps track of support for guest login */ private Boolean allowGuestLogin = null; - + private TenantService tenantService; private PersonService personService; @@ -65,6 +66,8 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC private TransactionService transactionService; + private boolean autoCreatePeopleOnLogin = true; + public AbstractAuthenticationComponent() { super(); @@ -79,10 +82,10 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC { this.allowGuestLogin = allowGuestLogin; } - + public void setTenantService(TenantService tenantService) { - this.tenantService = tenantService; + this.tenantService = tenantService; } public void setPersonService(PersonService personService) @@ -120,6 +123,16 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC return personService; } + public boolean isAutoCreatePeopleOnLogin() + { + return autoCreatePeopleOnLogin; + } + + public void setAutoCreatePeopleOnLogin(boolean autoCreatePeopleOnLogin) + { + this.autoCreatePeopleOnLogin = autoCreatePeopleOnLogin; + } + public void authenticate(String userName, char[] password) throws AuthenticationException { // Support guest login from the login screen @@ -148,7 +161,7 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC public Authentication setCurrentUser(String userName, UserNameValidationMode validationMode) { - switch(validationMode) + switch (validationMode) { case NONE: return setCurrentUserImpl(userName); @@ -157,7 +170,7 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC return setCurrentUser(userName); } } - + public Authentication setCurrentUser(final String userName) throws AuthenticationException { if (isSystemUserName(userName)) @@ -232,8 +245,8 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC throw new AuthenticationException(ae.getMessage(), ae); } finally - { - // Support for logging tenantdomain / username (via log4j NDC) + { + // Support for logging tenantdomain / username (via log4j NDC) AuthenticationUtil.logNDC(userName); } } @@ -283,8 +296,7 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC } /** - * Set the system user as the current user - * note: for MT, will set to default domain only + * Set the system user as the current user note: for MT, will set to default domain only * * @return Authentication */ @@ -294,8 +306,7 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC } /** - * Get the name of the system user - * note: for MT, will get system for default domain only + * Get the name of the system user note: for MT, will get system for default domain only * * @return String */ @@ -303,20 +314,19 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC { return AuthenticationUtil.SYSTEM_USER_NAME; } - + /** * Is this the system user ? - * + * * @return boolean - */ + */ public boolean isSystemUserName(String userName) { - return (getSystemUserName().equals(tenantService.getBaseNameUser(userName))); + return (getSystemUserName().equals(tenantService.getBaseNameUser(userName))); } /** - * Get the name of the Guest User - * note: for MT, will get guest for default domain only + * Get the name of the Guest User note: for MT, will get guest for default domain only * * @return String */ @@ -327,18 +337,17 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC private String getGuestUserName(String tenantDomain) { - return tenantService.getDomainUser(getGuestUserName(), tenantDomain); + return tenantService.getDomainUser(getGuestUserName(), tenantDomain); } - + /** - * Set the guest user as the current user. - * note: for MT, will set to default domain only + * Set the guest user as the current user. note: for MT, will set to default domain only */ public Authentication setGuestUserAsCurrentUser() throws AuthenticationException { - return setGuestUserAsCurrentUser(TenantService.DEFAULT_DOMAIN); + return setGuestUserAsCurrentUser(TenantService.DEFAULT_DOMAIN); } - + /** * Set the guest user as the current user. */ @@ -359,7 +368,7 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC { if (allowGuestLogin.booleanValue()) { - return setCurrentUser(getGuestUserName(tenantDomain)); + return setCurrentUser(getGuestUserName(tenantDomain)); } else { @@ -368,10 +377,10 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC } } - + private boolean isGuestUserName(String userName) { - return (PermissionService.GUEST_AUTHORITY.equalsIgnoreCase(tenantService.getBaseNameUser(userName))); + return (PermissionService.GUEST_AUTHORITY.equalsIgnoreCase(tenantService.getBaseNameUser(userName))); } protected abstract boolean implementationAllowsGuestLogin(); @@ -447,7 +456,8 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC NodeRef userNode = personService.getPerson(userName); if (userNode != null) { - // Get the person name and use that as the current user to line up with permission checks + // Get the person name and use that as the current user to line up with permission + // checks return (String) nodeService.getProperty(userNode, ContentModel.PROP_USERNAME); } else @@ -458,12 +468,23 @@ public abstract class AbstractAuthenticationComponent implements AuthenticationC } else { + if (autoCreatePeopleOnLogin && (userName != null) && !userName.equals(AuthenticationUtil.getSystemUserName())) + { + if (personService.createMissingPeople()) + { + AuthorityType authorityType = AuthorityType.getAuthorityType(userName); + if (authorityType == AuthorityType.USER) + { + personService.getPerson(userName); + } + } + } // Get user name return userName; - } + } } - }, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantService.getUserDomain(userName))); - + }, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantService.getUserDomain(userName))); + return setCurrentUserImpl(name); } catch (AuthenticationException ae) diff --git a/source/java/org/alfresco/repo/security/authentication/AuthenticationTest.java b/source/java/org/alfresco/repo/security/authentication/AuthenticationTest.java index f44ea52c54..0c260fb48b 100644 --- a/source/java/org/alfresco/repo/security/authentication/AuthenticationTest.java +++ b/source/java/org/alfresco/repo/security/authentication/AuthenticationTest.java @@ -245,7 +245,7 @@ public class AuthenticationTest extends TestCase userName = pubAuthenticationService.getCurrentUserName(); assertEquals("andy", userName); // get Person - assertFalse(pubPersonService.personExists(userName)); + assertTrue(pubPersonService.personExists(userName)); AuthenticationUtil.runAs(new RunAsWork() { diff --git a/source/java/org/alfresco/repo/security/person/PersonDaoImpl.java b/source/java/org/alfresco/repo/security/person/PersonDaoImpl.java index 20e7c65c0a..68718c7710 100644 --- a/source/java/org/alfresco/repo/security/person/PersonDaoImpl.java +++ b/source/java/org/alfresco/repo/security/person/PersonDaoImpl.java @@ -51,7 +51,7 @@ import org.springframework.orm.hibernate3.support.HibernateDaoSupport; public class PersonDaoImpl extends HibernateDaoSupport implements PersonDao { - private static final String QUERY_PERSON_GET_PERSON = "person.getPerson"; + private static final String QUERY_PERSON_GET_PERSON_IGNORE_CASE = "person.getPersonIgnoreCase"; private static final String QUERY_PERSON_GET_ALL_PEOPLE = "person.getAllPeople"; @@ -75,9 +75,32 @@ public class PersonDaoImpl extends HibernateDaoSupport implements PersonDao this.tenantService = tenantService; } + public void init() + { + qNamePropId = qnameDAO.getOrCreateQName(ContentModel.PROP_USERNAME).getFirst(); + qNameTypeId = qnameDAO.getOrCreateQName(ContentModel.TYPE_PERSON).getFirst(); + } + @SuppressWarnings("unchecked") public List getPersonOrNull(final String searchUserName, UserNameMatcher matcher) { + /* + * Related JIRA: + * https://issues.alfresco.com/jira/browse/MOB-387 + * https://issues.alfresco.com/jira/browse/ETHREEOH-1431 + * https://issues.alfresco.com/jira/browse/ETWOTWO-1012 + * + * When usernames are case-insensitive, it could happen that the DB is NOT. + * DB queries should therefore return values regardless of the DB collation. + * The original, case-preserving username is stored with the node properties. + * To solve the query issue, a LOWERCASE version of the username is stored on + * the path (alf_child_assoc.qname_localname). This is queried for using the + * lowercase version of the searched-for username. The case results pruning + * is done (as it always was) as a post-search task. + * + * Note that the upgrade scripts had to change to force lowercase names as well. + */ + final StoreRef personStoreRef = tenantService.getName(storeRef); List answer = new ArrayList(); @@ -86,11 +109,10 @@ public class PersonDaoImpl extends HibernateDaoSupport implements PersonDao { public Object doInHibernate(Session session) { - SQLQuery query = (SQLQuery) session.getNamedQuery(QUERY_PERSON_GET_PERSON); + SQLQuery query = (SQLQuery) session.getNamedQuery(QUERY_PERSON_GET_PERSON_IGNORE_CASE); query.setParameter("qnamePropId", qNamePropId); query.setParameter("qnameTypeId", qNameTypeId); - query.setParameter("userName1", searchUserName); - query.setParameter("userName2", searchUserName); + query.setParameter("userNameLowerCase", searchUserName.toLowerCase()); // Lowercase: ETHREEOH-1431 query.setParameter("False", Boolean.FALSE); query.setParameter("storeProtocol", personStoreRef.getProtocol()); query.setParameter("storeIdentifier", personStoreRef.getIdentifier()); @@ -121,12 +143,6 @@ public class PersonDaoImpl extends HibernateDaoSupport implements PersonDao } - public void init() - { - qNamePropId = qnameDAO.getOrCreateQName(ContentModel.PROP_USERNAME).getFirst(); - qNameTypeId = qnameDAO.getOrCreateQName(ContentModel.TYPE_PERSON).getFirst(); - } - @SuppressWarnings("unchecked") public Set getAllPeople() { diff --git a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java index 005f090909..66e70819f0 100644 --- a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java +++ b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java @@ -68,7 +68,6 @@ import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.GUID; -import org.alfresco.util.Pair; import org.alfresco.util.PropertyCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -596,7 +595,11 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per properties.put(ContentModel.PROP_USERNAME, userName); properties.put(ContentModel.PROP_SIZE_CURRENT, 0L); - NodeRef personRef = nodeService.createNode(getPeopleContainer(), ContentModel.ASSOC_CHILDREN, QName.createQName("cm", userName, namespacePrefixResolver), ContentModel.TYPE_PERSON, + NodeRef personRef = nodeService.createNode( + getPeopleContainer(), + ContentModel.ASSOC_CHILDREN, + QName.createQName("cm", userName.toLowerCase(), namespacePrefixResolver), // Lowercase: ETHREEOH-1431 + ContentModel.TYPE_PERSON, properties).getChildRef(); return personRef; } diff --git a/source/java/org/alfresco/repo/security/person/UserNameMatcher.java b/source/java/org/alfresco/repo/security/person/UserNameMatcher.java index 63652c68fa..c71acb2586 100644 --- a/source/java/org/alfresco/repo/security/person/UserNameMatcher.java +++ b/source/java/org/alfresco/repo/security/person/UserNameMatcher.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2009 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -27,16 +27,12 @@ package org.alfresco.repo.security.person; /** * Check if userNames match * @author andyh - * + * @since 3.1 */ public interface UserNameMatcher { /** * Do the two user names match? - * - * @param userName1 - * @param userName2 - * @return */ public boolean matches(String userName1, String userName2); diff --git a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java index 4ae010d79e..1b23041ff4 100755 --- a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java +++ b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java @@ -1,1254 +1,1254 @@ -/* - * Copyright (C) 2005-2009 Alfresco Software Limited. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program 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 General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - * As a special exception to the terms and conditions of version 2.0 of - * the GPL, you may redistribute this Program in connection with Free/Libre - * and Open Source Software ("FLOSS") applications as described in Alfresco's - * FLOSS exception. You should have recieved a copy of the text describing - * the FLOSS exception, and it is also available here: - * http://www.alfresco.com/legal/licensing" - */ -package org.alfresco.repo.tenant; - -import java.io.File; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; -import java.util.Set; -import java.util.regex.Pattern; - -import javax.transaction.UserTransaction; - -import net.sf.acegisecurity.providers.encoding.PasswordEncoder; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.admin.RepoModelDefinition; -import org.alfresco.repo.attributes.BooleanAttributeValue; -import org.alfresco.repo.attributes.MapAttribute; -import org.alfresco.repo.attributes.MapAttributeValue; -import org.alfresco.repo.attributes.StringAttributeValue; -import org.alfresco.repo.content.TenantRoutingFileContentStore; -import org.alfresco.repo.dictionary.DictionaryComponent; -import org.alfresco.repo.importer.ImporterBootstrap; -import org.alfresco.repo.node.db.DbNodeServiceImpl; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.repo.site.SiteAVMBootstrap; -import org.alfresco.repo.usage.UserUsageBootstrapJob; -import org.alfresco.repo.usage.UserUsageTrackingComponent; -import org.alfresco.repo.workflow.WorkflowDeployer; -import org.alfresco.service.cmr.admin.RepoAdminService; -import org.alfresco.service.cmr.attributes.AttributeService; -import org.alfresco.service.cmr.module.ModuleService; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.view.RepositoryExporterService; -import org.alfresco.service.cmr.workflow.WorkflowDefinition; -import org.alfresco.service.cmr.workflow.WorkflowService; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.ParameterCheck; -import org.alfresco.util.PropertyCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; - -/** - * MT Admin Service Implementation. - * - */ - -public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationContextAware -{ - // Logger - private static Log logger = LogFactory.getLog(MultiTAdminServiceImpl.class); - - // Keep hold of the app context - private ApplicationContext ctx; - - // Dependencies - private NodeService nodeService; - private DictionaryComponent dictionaryComponent; - private RepoAdminService repoAdminService; - private AuthenticationComponent authenticationComponent; - private TransactionService transactionService; - private MultiTServiceImpl tenantService; - private AttributeService attributeService; - private PasswordEncoder passwordEncoder; - private TenantRoutingFileContentStore tenantFileContentStore; - private WorkflowService workflowService; - private RepositoryExporterService repositoryExporterService; - private ModuleService moduleService; - private SiteAVMBootstrap siteAVMBootstrap; - private List workflowDeployers = new ArrayList(); - - private String baseAdminUsername = null; - - /* - * 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. - * - * The following PCRE-style - * regex defines a valid label within a FQDN: - * - * ^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$ - * - * Less formally: - * - * o Case insensitive - * o First/last character: alphanumeric - * o Interior characters: alphanumeric plus hyphen - * o Minimum length: 2 characters - * o Maximum length: 63 characters - * - * The FQDN (fully qualified domain name) has the following constraints: - * - * o Maximum 255 characters (***) - * o Must contain at least one alpha - * - * Note: (***) Due to various internal restrictions (such as store identifier) we restrict tenant ids to 75 characters. - */ - - protected final static String REGEX_VALID_DNS_LABEL = "^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$"; - - protected final static String REGEX_CONTAINS_ALPHA = "^(.*)[a-zA-Z](.*)$"; - - protected final static int MAX_LEN = 75; - - public void setNodeService(DbNodeServiceImpl dbNodeService) - { - this.nodeService = dbNodeService; - } - - public void setDictionaryComponent(DictionaryComponent dictionaryComponent) - { - this.dictionaryComponent = dictionaryComponent; - } - - public void setRepoAdminService(RepoAdminService repoAdminService) - { - this.repoAdminService = repoAdminService; - } - - public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) - { - this.authenticationComponent = authenticationComponent; - } - - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - public void setTenantService(MultiTServiceImpl tenantService) - { - this.tenantService = tenantService; - } - - public void setAttributeService(AttributeService attributeService) - { - this.attributeService = attributeService; - } - - public void setPasswordEncoder(PasswordEncoder passwordEncoder) - { - this.passwordEncoder = passwordEncoder; - } - - public void setTenantFileContentStore(TenantRoutingFileContentStore tenantFileContentStore) - { - this.tenantFileContentStore = tenantFileContentStore; - } - - public void setWorkflowService(WorkflowService workflowService) - { - this.workflowService = workflowService; - } - - public void setRepositoryExporterService(RepositoryExporterService repositoryExporterService) - { - this.repositoryExporterService = repositoryExporterService; - } - - /** - * @deprecated see setWorkflowDeployers - */ - public void setWorkflowDeployer(WorkflowDeployer workflowDeployer) - { - // NOOP - logger.warn(WARN_MSG); - } - - public void setModuleService(ModuleService moduleService) - { - this.moduleService = moduleService; - } - - public void setSiteAVMBootstrap(SiteAVMBootstrap siteAVMBootstrap) - { - this.siteAVMBootstrap = siteAVMBootstrap; - } - - public void setBaseAdminUsername(String baseAdminUsername) - { - this.baseAdminUsername = baseAdminUsername; - } - - public static final String PROTOCOL_STORE_USER = "user"; - public static final String PROTOCOL_STORE_WORKSPACE = "workspace"; - public static final String PROTOCOL_STORE_SYSTEM = "system"; - public static final String PROTOCOL_STORE_ARCHIVE = "archive"; - public static final String STORE_BASE_ID_USER = "alfrescoUserStore"; - public static final String STORE_BASE_ID_SYSTEM = "system"; - public static final String STORE_BASE_ID_VERSION1 = "lightWeightVersionStore"; // deprecated - public static final String STORE_BASE_ID_VERSION2 = "version2Store"; - public static final String STORE_BASE_ID_SPACES = "SpacesStore"; - - - private static final String TENANTS_ATTRIBUTE_PATH = "alfresco-tenants"; - private static final String TENANT_ATTRIBUTE_ENABLED = "enabled"; - private static final String TENANT_ROOT_CONTENT_STORE_DIR = "rootContentStoreDir"; - - private List tenantDeployers = new ArrayList(); - - private static final String WARN_MSG = "Please update your alfresco/extension/mt/mt-admin-context.xml to use baseMultiTAdminService (see latest alfresco/extension/mt/mt-admin-context.xml.sample)"; - - protected void checkProperties() - { - if (moduleService == null || siteAVMBootstrap == null || baseAdminUsername == null) - { - logger.warn(WARN_MSG); - } - PropertyCheck.mandatory(this, "NodeService", nodeService); - PropertyCheck.mandatory(this, "DictionaryComponent", dictionaryComponent); - PropertyCheck.mandatory(this, "RepoAdminService", repoAdminService); - PropertyCheck.mandatory(this, "TransactionService", transactionService); - PropertyCheck.mandatory(this, "TenantService", tenantService); - PropertyCheck.mandatory(this, "AttributeService", attributeService); - PropertyCheck.mandatory(this, "PasswordEncoder", passwordEncoder); - PropertyCheck.mandatory(this, "TenantFileContentStore", tenantFileContentStore); - PropertyCheck.mandatory(this, "WorkflowService", workflowService); - PropertyCheck.mandatory(this, "RepositoryExporterService", repositoryExporterService); - PropertyCheck.mandatory(this, "siteAVMBootstrap", siteAVMBootstrap); - PropertyCheck.mandatory(this, "moduleService", moduleService); - PropertyCheck.mandatory(this, "baseAdminUsername", baseAdminUsername); - } - - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException - { - this.ctx = applicationContext; - } - - public void startTenants() - { - checkProperties(); - - AuthenticationUtil.setMtEnabled(true); - - // initialise the tenant admin service and status of tenants (using attribute service) - // note: this requires that the repository schema has already been initialised - - // register dictionary - to allow enable/disable tenant callbacks - register(dictionaryComponent); - - // register file store - to allow enable/disable tenant callbacks - register(tenantFileContentStore); - - UserTransaction userTransaction = transactionService.getUserTransaction(); - - try - { - authenticationComponent.setSystemUserAsCurrentUser(); - userTransaction.begin(); - - // bootstrap Tenant Service internal cache - List tenants = getAllTenants(); - - int enabledCount = 0; - int disabledCount = 0; - - for (Tenant tenant : tenants) - { - if (tenant.isEnabled()) - { - // this will also call tenant deployers registered so far ... - enableTenant(tenant.getTenantDomain(), true); - enabledCount++; - } - else - { - // explicitly disable, without calling disableTenant callback - disableTenant(tenant.getTenantDomain(), false); - disabledCount++; - } - } - - tenantService.register(this); // callback to refresh tenantStatus cache - - userTransaction.commit(); - - if (logger.isInfoEnabled()) - { - logger.info(String.format("Alfresco Multi-Tenant startup - %d enabled tenants, %d disabled tenants", - enabledCount, disabledCount)); - } - } - catch(Throwable e) - { - // rollback the transaction - try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {} - throw new AlfrescoRuntimeException("Failed to bootstrap tenants", e); - } - finally - { - authenticationComponent.clearCurrentSecurityContext(); - } - } - - public void stopTenants() - { - tenantDeployers.clear(); - tenantDeployers = null; - } - - /** - * @see TenantAdminService.createTenant() - */ - public void createTenant(final String tenantDomain, final char[] tenantAdminRawPassword) - { - createTenant(tenantDomain, tenantAdminRawPassword, null); - } - - /** - * @see TenantAdminService.createTenant() - */ - public void createTenant(final String tenantDomain, final char[] tenantAdminRawPassword, String rootContentStoreDir) - { - ParameterCheck.mandatory("tenantAdminRawPassword", tenantAdminRawPassword); - - initTenant(tenantDomain, rootContentStoreDir); - - try - { - // note: runAs would cause auditable property "creator" to be "admin" instead of "System@xxx" - AuthenticationUtil.pushAuthentication(); - AuthenticationUtil.setFullyAuthenticatedUser(getSystemUser(tenantDomain)); - - dictionaryComponent.init(); - tenantFileContentStore.init(); - - // create tenant-specific stores - ImporterBootstrap userImporterBootstrap = (ImporterBootstrap)ctx.getBean("userBootstrap"); - bootstrapUserTenantStore(userImporterBootstrap, tenantDomain, tenantAdminRawPassword); - - ImporterBootstrap systemImporterBootstrap = (ImporterBootstrap)ctx.getBean("systemBootstrap"); - bootstrapSystemTenantStore(systemImporterBootstrap, tenantDomain); - - // deprecated - ImporterBootstrap versionImporterBootstrap = (ImporterBootstrap)ctx.getBean("versionBootstrap"); - bootstrapVersionTenantStore(versionImporterBootstrap, tenantDomain); - - ImporterBootstrap version2ImporterBootstrap = (ImporterBootstrap)ctx.getBean("version2Bootstrap"); - bootstrapVersionTenantStore(version2ImporterBootstrap, tenantDomain); - - ImporterBootstrap spacesArchiveImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesArchiveBootstrap"); - bootstrapSpacesArchiveTenantStore(spacesArchiveImporterBootstrap, tenantDomain); - - ImporterBootstrap spacesImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesBootstrap"); - bootstrapSpacesTenantStore(spacesImporterBootstrap, tenantDomain); - - siteAVMBootstrap.bootstrap(); - - // 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(); - } - finally - { - AuthenticationUtil.popAuthentication(); - } - - logger.info("Tenant created: " + tenantDomain); - } - - /** - * Export tenant - equivalent to the tenant admin running a 'complete repo' export from the Web Client Admin - */ - public void exportTenant(final String tenantDomain, final File directoryDestination) - { - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() - { - repositoryExporterService.export(directoryDestination, tenantDomain); - return null; - } - }, getSystemUser(tenantDomain)); - - logger.info("Tenant exported: " + tenantDomain); - } - - /** - * Create tenant by restoring from a complete repository export. This is equivalent to a bootstrap import using restore-context.xml. - */ - public void importTenant(final String tenantDomain, final File directorySource, String rootContentStoreDir) - { - initTenant(tenantDomain, rootContentStoreDir); - - try - { - // note: runAs would cause auditable property "creator" to be "admin" instead of "System@xxx" - AuthenticationUtil.pushAuthentication(); - AuthenticationUtil.setFullyAuthenticatedUser(getSystemUser(tenantDomain)); - - dictionaryComponent.init(); - tenantFileContentStore.init(); - - // import tenant-specific stores - importBootstrapUserTenantStore(tenantDomain, directorySource); - importBootstrapSystemTenantStore(tenantDomain, directorySource); - importBootstrapVersionTenantStore(tenantDomain, directorySource); - importBootstrapSpacesArchiveTenantStore(tenantDomain, directorySource); - importBootstrapSpacesModelsTenantStore(tenantDomain, directorySource); - importBootstrapSpacesTenantStore(tenantDomain, directorySource); - - // 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(); - } - finally - { - AuthenticationUtil.popAuthentication(); - } - - logger.info("Tenant imported: " + tenantDomain); - } - - public boolean existsTenant(String tenantDomain) - { - // Check that all the passed values are not null - ParameterCheck.mandatory("tenantDomain", tenantDomain); - - return (getTenantAttributes(tenantDomain) != null); - } - - private void putTenantAttributes(String tenantDomain, Tenant tenant) - { - if (! attributeService.exists(TENANTS_ATTRIBUTE_PATH)) - { - // bootstrap - attributeService.setAttribute("", TENANTS_ATTRIBUTE_PATH, new MapAttributeValue()); - } - - MapAttribute tenantProps = new MapAttributeValue(); - tenantProps.put(TENANT_ATTRIBUTE_ENABLED, new BooleanAttributeValue(tenant.isEnabled())); - tenantProps.put(TENANT_ROOT_CONTENT_STORE_DIR, new StringAttributeValue(tenant.getRootContentStoreDir())); - - attributeService.setAttribute(TENANTS_ATTRIBUTE_PATH, tenantDomain, tenantProps); - - // update tenant status cache - ((MultiTServiceImpl)tenantService).putTenant(tenantDomain, tenant); - } - - private Tenant getTenantAttributes(String tenantDomain) - { - if (attributeService.exists(TENANTS_ATTRIBUTE_PATH+"/"+tenantDomain)) - { - MapAttribute map = (MapAttribute)attributeService.getAttribute(TENANTS_ATTRIBUTE_PATH+"/"+tenantDomain); - if (map != null) - { - return new Tenant(tenantDomain, - map.get(TENANT_ATTRIBUTE_ENABLED).getBooleanValue(), - map.get(TENANT_ROOT_CONTENT_STORE_DIR).getStringValue()); - } - } - - return null; - } - - public void enableTenant(String tenantDomain) - { - if (! existsTenant(tenantDomain)) - { - throw new RuntimeException("Tenant does not exist: " + tenantDomain); - } - - if (isEnabledTenant(tenantDomain)) - { - logger.warn("Tenant already enabled: " + tenantDomain); - } - - enableTenant(tenantDomain, true); - } - - private void enableTenant(String tenantDomain, boolean notifyTenantDeployers) - { - // Check that all the passed values are not null - ParameterCheck.mandatory("tenantDomain", tenantDomain); - - Tenant tenant = getTenantAttributes(tenantDomain); - tenant = new Tenant(tenantDomain, true, tenant.getRootContentStoreDir()); // enable - putTenantAttributes(tenantDomain, tenant); - - if (notifyTenantDeployers) - { - // notify listeners that tenant has been enabled - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() - { - for (TenantDeployer tenantDeployer : tenantDeployers) - { - tenantDeployer.onEnableTenant(); - } - return null; - } - }, getSystemUser(tenantDomain)); - } - - logger.info("Tenant enabled: " + tenantDomain); - } - - public void disableTenant(String tenantDomain) - { - if (! existsTenant(tenantDomain)) - { - throw new RuntimeException("Tenant does not exist: " + tenantDomain); - } - - if (! isEnabledTenant(tenantDomain)) - { - logger.warn("Tenant already disabled: " + tenantDomain); - } - - disableTenant(tenantDomain, true); - } - - public void disableTenant(String tenantDomain, boolean notifyTenantDeployers) - { - if (notifyTenantDeployers) - { - // notify listeners that tenant has been disabled - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() - { - for (TenantDeployer tenantDeployer : tenantDeployers) - { - tenantDeployer.onDisableTenant(); - } - return null; - } - }, getSystemUser(tenantDomain)); - } - - // update tenant attributes / tenant cache - need to disable after notifying listeners (else they cannot disable) - Tenant tenant = getTenantAttributes(tenantDomain); - tenant = new Tenant(tenantDomain, false, tenant.getRootContentStoreDir()); // disable - putTenantAttributes(tenantDomain, tenant); - - logger.info("Tenant disabled: " + tenantDomain); - } - - public boolean isEnabledTenant(String tenantDomain) - { - // Check that all the passed values are not null - ParameterCheck.mandatory("tenantDomain", 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); - - Tenant tenant = getTenantAttributes(tenantDomain); - if (tenant != null) - { - return tenant.getRootContentStoreDir(); - } - - return null; - } - - protected void putRootContentStoreDir(String tenantDomain, String rootContentStoreDir) - { - Tenant tenant = getTenantAttributes(tenantDomain); - tenant = new Tenant(tenantDomain, tenant.isEnabled(), rootContentStoreDir); - putTenantAttributes(tenantDomain, tenant); - } - - public Tenant getTenant(String tenantDomain) - { - if (! existsTenant(tenantDomain)) - { - throw new RuntimeException("Tenant does not exist: " + tenantDomain); - } - - return new Tenant(tenantDomain, isEnabledTenant(tenantDomain), getRootContentStoreDir(tenantDomain)); - } - - /** - * @see TenantAdminService.deleteTenant() - */ - public void deleteTenant(String tenantDomain) - { - if (! existsTenant(tenantDomain)) - { - throw new RuntimeException("Tenant does not exist: " + tenantDomain); - } - else - { - try - { - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() - { - List workflowDefs = workflowService.getDefinitions(); - if (workflowDefs != null) - { - for (WorkflowDefinition workflowDef : workflowDefs) - { - workflowService.undeployDefinition(workflowDef.getId()); - } - } - - List messageResourceBundles = repoAdminService.getMessageBundles(); - if (messageResourceBundles != null) - { - for (String messageResourceBundle : messageResourceBundles) - { - repoAdminService.undeployMessageBundle(messageResourceBundle); - } - } - - List models = repoAdminService.getModels(); - if (models != null) - { - for (RepoModelDefinition model : models) - { - repoAdminService.undeployModel(model.getRepoName()); - } - } - - return null; - } - }, getSystemUser(tenantDomain)); - - final String tenantAdminUser = getTenantAdminUser(tenantDomain); - - // delete tenant-specific stores - nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_WORKSPACE, STORE_BASE_ID_SPACES))); - nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_ARCHIVE, STORE_BASE_ID_SPACES))); - nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_WORKSPACE, STORE_BASE_ID_VERSION1))); - nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_WORKSPACE, STORE_BASE_ID_VERSION2))); - 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() - { - public Object doWork() - { - for (TenantDeployer tenantDeployer : tenantDeployers) - { - tenantDeployer.onDisableTenant(); - } - return null; - } - }, getSystemUser(tenantDomain)); - - // remove tenant - attributeService.removeAttribute(TENANTS_ATTRIBUTE_PATH, tenantDomain); - } - catch (Throwable t) - { - throw new AlfrescoRuntimeException("Failed to delete tenant: " + tenantDomain, t); - } - } - } - - /** - * @see TenantAdminService.getAllTenants() - */ - public List getAllTenants() - { - MapAttribute map = (MapAttribute)attributeService.getAttribute(TENANTS_ATTRIBUTE_PATH); - - List tenants = new ArrayList(); - - if (map != null) - { - // note: getAllTenants is called first, by TenantDeployer - hence need to initialise the TenantService status cache - Set tenantDomains = map.keySet(); - - for (String tenantDomain : tenantDomains) - { - Tenant tenant = getTenantAttributes(tenantDomain); - tenants.add(new Tenant(tenantDomain, tenant.isEnabled(), tenant.getRootContentStoreDir())); - } - } - - return tenants; // list of tenants or empty list - } - - private void importBootstrapSystemTenantStore(String tenantDomain, File directorySource) - { - // Import Bootstrap (restore) Tenant-Specific Version Store - Properties bootstrapView = new Properties(); - bootstrapView.put("path", "/"); - bootstrapView.put("location", directorySource.getPath()+"/"+tenantDomain+"_system.acp"); - - List bootstrapViews = new ArrayList(1); - bootstrapViews.add(bootstrapView); - - ImporterBootstrap systemImporterBootstrap = (ImporterBootstrap)ctx.getBean("systemBootstrap"); - systemImporterBootstrap.setBootstrapViews(bootstrapViews); - - bootstrapSystemTenantStore(systemImporterBootstrap, tenantDomain); - } - - private void bootstrapSystemTenantStore(ImporterBootstrap systemImporterBootstrap, String tenantDomain) - { - // Bootstrap Tenant-Specific System Store - StoreRef bootstrapStoreRef = systemImporterBootstrap.getStoreRef(); - StoreRef tenantBootstrapStoreRef = new StoreRef(bootstrapStoreRef.getProtocol(), tenantService.getName(bootstrapStoreRef.getIdentifier(), tenantDomain)); - systemImporterBootstrap.setStoreUrl(tenantBootstrapStoreRef.toString()); - - // override default property (workspace://SpacesStore) - List mustNotExistStoreUrls = new ArrayList(); - mustNotExistStoreUrls.add(new StoreRef(PROTOCOL_STORE_WORKSPACE, tenantService.getName(STORE_BASE_ID_USER, tenantDomain)).toString()); - systemImporterBootstrap.setMustNotExistStoreUrls(mustNotExistStoreUrls); - - systemImporterBootstrap.bootstrap(); - - // reset since systemImporter is singleton (hence reused) - systemImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); - - logger.debug("Bootstrapped store: " + tenantService.getBaseName(tenantBootstrapStoreRef)); - } - - private void importBootstrapUserTenantStore(String tenantDomain, File directorySource) - { - // Import Bootstrap (restore) Tenant-Specific User Store - Properties bootstrapView = new Properties(); - bootstrapView.put("path", "/"); - bootstrapView.put("location", directorySource.getPath()+"/"+tenantDomain+"_users.acp"); - - List bootstrapViews = new ArrayList(1); - bootstrapViews.add(bootstrapView); - - ImporterBootstrap userImporterBootstrap = (ImporterBootstrap)ctx.getBean("userBootstrap"); - userImporterBootstrap.setBootstrapViews(bootstrapViews); - - bootstrapUserTenantStore(userImporterBootstrap, tenantDomain, null); - } - - private 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(); - 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)); - } - - userImporterBootstrap.bootstrap(); - - logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); - } - - 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) - { - // 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)); - } - - private void importBootstrapSpacesArchiveTenantStore(String tenantDomain, File directorySource) - { - // Import Bootstrap (restore) Tenant-Specific Spaces Archive Store - 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) - { - // Bootstrap Tenant-Specific Spaces Archive Store - StoreRef bootstrapStoreRef = spacesArchiveImporterBootstrap.getStoreRef(); - bootstrapStoreRef = new StoreRef(bootstrapStoreRef.getProtocol(), tenantService.getName(bootstrapStoreRef.getIdentifier(), tenantDomain)); - spacesArchiveImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); - - // override default property (archive://SpacesStore) - List mustNotExistStoreUrls = new ArrayList(); - mustNotExistStoreUrls.add(bootstrapStoreRef.toString()); - spacesArchiveImporterBootstrap.setMustNotExistStoreUrls(mustNotExistStoreUrls); - - spacesArchiveImporterBootstrap.bootstrap(); - - logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); - } - - private void importBootstrapSpacesModelsTenantStore(String tenantDomain, File directorySource) - { - // Import Bootstrap (restore) Tenant-Specific Spaces Store - Properties bootstrapView = new Properties(); - bootstrapView.put("path", "/"); - bootstrapView.put("location", directorySource.getPath()+"/"+tenantDomain+"_models.acp"); - - List bootstrapViews = new ArrayList(1); - bootstrapViews.add(bootstrapView); - - ImporterBootstrap spacesImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesBootstrap"); - spacesImporterBootstrap.setBootstrapViews(bootstrapViews); - - bootstrapSpacesTenantStore(spacesImporterBootstrap, tenantDomain); - } - - private void importBootstrapSpacesTenantStore(String tenantDomain, File directorySource) - { - // Import Bootstrap (restore) Tenant-Specific Spaces Store - Properties bootstrapView = new Properties(); - bootstrapView.put("path", "/"); - bootstrapView.put("location", directorySource.getPath()+"/"+tenantDomain+"_spaces.acp"); - bootstrapView.put("uuidBinding", "UPDATE_EXISTING"); - - List bootstrapViews = new ArrayList(1); - bootstrapViews.add(bootstrapView); - - ImporterBootstrap spacesImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesBootstrap"); - spacesImporterBootstrap.setBootstrapViews(bootstrapViews); - - spacesImporterBootstrap.setUseExistingStore(true); - - bootstrapSpacesTenantStore(spacesImporterBootstrap, tenantDomain); - } - - private void bootstrapSpacesTenantStore(ImporterBootstrap spacesImporterBootstrap, String tenantDomain) - { - // Bootstrap Tenant-Specific Spaces Store - StoreRef bootstrapStoreRef = spacesImporterBootstrap.getStoreRef(); - bootstrapStoreRef = new StoreRef(bootstrapStoreRef.getProtocol(), tenantService.getName(bootstrapStoreRef.getIdentifier(), tenantDomain)); - spacesImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); - - // override admin username property - Properties props = spacesImporterBootstrap.getConfiguration(); - props.put("alfresco_user_store.adminusername", getTenantAdminUser(tenantDomain)); - - // override guest username property - props.put("alfresco_user_store.guestusername", getTenantGuestUser(tenantDomain)); - - spacesImporterBootstrap.bootstrap(); - - // calculate any missing usages - UserUsageTrackingComponent userUsageTrackingComponent = (UserUsageTrackingComponent)ctx.getBean(UserUsageBootstrapJob.KEY_COMPONENT); - userUsageTrackingComponent.bootstrapInternal(); - - logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); - } - - public void deployTenants(final TenantDeployer deployer, Log logger) - { - 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(); - authenticationComponent.setSystemUserAsCurrentUser(); - - List tenants = null; - try - { - userTransaction.begin(); - tenants = getAllTenants(); - userTransaction.commit(); - } - catch(Throwable e) - { - // rollback the transaction - try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {} - throw new AlfrescoRuntimeException("Failed to get tenants", e); - } - finally - { - authenticationComponent.clearCurrentSecurityContext(); - } - - for (Tenant tenant : tenants) - { - if (tenant.isEnabled()) - { - try - { - // deploy within context of tenant domain - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() - { - // init the service within tenant context - deployer.init(); - return null; - } - }, getSystemUser(tenant.getTenantDomain())); - - } - catch (Throwable e) - { - logger.error("Deployment failed" + e); - - StringWriter stringWriter = new StringWriter(); - e.printStackTrace(new PrintWriter(stringWriter)); - logger.error(stringWriter.toString()); - - // tenant deploy failure should not necessarily affect other tenants - } - } - } - } - } - - public void undeployTenants(final TenantDeployer deployer, Log logger) - { - 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(); - authenticationComponent.setSystemUserAsCurrentUser(); - - List tenants = null; - try - { - userTransaction.begin(); - tenants = getAllTenants(); - userTransaction.commit(); - } - catch(Throwable e) - { - // rollback the transaction - try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {} - try {authenticationComponent.clearCurrentSecurityContext(); } catch (Exception ex) {} - throw new AlfrescoRuntimeException("Failed to get tenants", e); - } - - try - { - AuthenticationUtil.pushAuthentication(); - for (Tenant tenant : tenants) - { - if (tenant.isEnabled()) - { - try - { - // undeploy within context of tenant domain - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() - { - // destroy the service within tenant context - deployer.destroy(); - return null; - } - }, getSystemUser(tenant.getTenantDomain())); - - } - catch (Throwable e) - { - logger.error("Undeployment failed" + e); - - StringWriter stringWriter = new StringWriter(); - e.printStackTrace(new PrintWriter(stringWriter)); - logger.error(stringWriter.toString()); - - // tenant undeploy failure should not necessarily affect other tenants - } - } - } - } - finally - { - AuthenticationUtil.popAuthentication(); - } - } - } - - public void register(TenantDeployer deployer) - { - if (deployer == null) - { - throw new AlfrescoRuntimeException("TenantDeployer must be provided"); - } - - if (! tenantDeployers.contains(deployer)) - { - tenantDeployers.add(deployer); - } - } - - public void unregister(TenantDeployer deployer) - { - if (deployer == null) - { - throw new AlfrescoRuntimeException("TenantDeployer must be provided"); - } - - if (tenantDeployers != null) - { - tenantDeployers.remove(deployer); - } - } - - public void register(WorkflowDeployer workflowDeployer) - { - if (workflowDeployer == null) - { - throw new AlfrescoRuntimeException("WorkflowDeployer must be provided"); - } - - if (! workflowDeployers.contains(workflowDeployer)) - { - workflowDeployers.add(workflowDeployer); - } - } - - public void resetCache(String tenantDomain) - { - if (existsTenant(tenantDomain)) - { - if (isEnabledTenant(tenantDomain)) - { - enableTenant(tenantDomain); - } - else - { - disableTenant(tenantDomain); - } - } - else - { - throw new AlfrescoRuntimeException("No such tenant " + tenantDomain); - } - } - - private void initTenant(String tenantDomain, String rootContentStoreDir) - { - validateTenantName(tenantDomain); - - if (existsTenant(tenantDomain)) - { - throw new AlfrescoRuntimeException("Tenant already exists: " + tenantDomain); - } - - if (rootContentStoreDir == null) - { - rootContentStoreDir = tenantFileContentStore.getDefaultRootDir(); - } - else - { - File tenantRootDir = new File(rootContentStoreDir); - if ((tenantRootDir.exists()) && (tenantRootDir.list().length != 0)) - { - throw new AlfrescoRuntimeException("Tenant root directory is not empty: " + rootContentStoreDir); - } - } - - // init - need to enable tenant (including tenant service) before stores bootstrap - Tenant tenant = new Tenant(tenantDomain, true, rootContentStoreDir); - putTenantAttributes(tenantDomain, tenant); - } - - private void validateTenantName(String tenantDomain) - { - ParameterCheck.mandatory("tenantDomain", tenantDomain); - - if (tenantDomain.length() > MAX_LEN) - { - throw new IllegalArgumentException(tenantDomain + " is not a valid tenant name (must be less than " + MAX_LEN + " characters)"); - } - - if (! Pattern.matches(REGEX_CONTAINS_ALPHA, tenantDomain)) - { - throw new IllegalArgumentException(tenantDomain + " is not a valid tenant name (must contain at least one alpha character)"); - } - - String[] dnsLabels = tenantDomain.split("\\."); - if (dnsLabels.length != 0) - { - for (int i = 0; i < dnsLabels.length; i++) - { - if (! Pattern.matches(REGEX_VALID_DNS_LABEL, dnsLabels[i])) - { - throw new IllegalArgumentException(dnsLabels[i] + " is not a valid DNS label (must match " + REGEX_VALID_DNS_LABEL + ")"); - } - } - } - else - { - if (! Pattern.matches(REGEX_VALID_DNS_LABEL, tenantDomain)) - { - throw new IllegalArgumentException(tenantDomain + " is not a valid DNS label (must match " + REGEX_VALID_DNS_LABEL + ")"); - } - } - } - - // tenant deployer/user services delegated to tenant service - - public boolean isEnabled() - { - return tenantService.isEnabled(); - } - - public String getCurrentUserDomain() - { - return tenantService.getCurrentUserDomain(); - } - - public String getUserDomain(String username) - { - return tenantService.getUserDomain(username); - } - - public String getBaseNameUser(String username) - { - return tenantService.getBaseNameUser(username); - } - - public String getDomainUser(String baseUsername, String tenantDomain) - { - return tenantService.getDomainUser(baseUsername, tenantDomain); - } - - public String getDomain(String name) - { - return tenantService.getDomain(name); - } - - // local helpers - - public String getBaseAdminUser() - { - // default for backwards compatibility only - eg. upgrade of existing MT instance (mt-admin-context.xml.sample) - if (baseAdminUsername != null) - { - return baseAdminUsername; - } - return AuthenticationUtil.getAdminUserName(); - } - - private String getSystemUser(String tenantDomain) - { - return tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain); - } - - private String getTenantAdminUser(String tenantDomain) - { - - return tenantService.getDomainUser(getBaseAdminUser(), tenantDomain); - } - - private String getTenantGuestUser(String tenantDomain) - { - return tenantService.getDomainUser(authenticationComponent.getGuestUserName(), tenantDomain); - } -} +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.tenant; + +import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.Set; +import java.util.regex.Pattern; + +import javax.transaction.UserTransaction; + +import net.sf.acegisecurity.providers.encoding.PasswordEncoder; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.admin.RepoModelDefinition; +import org.alfresco.repo.attributes.BooleanAttributeValue; +import org.alfresco.repo.attributes.MapAttribute; +import org.alfresco.repo.attributes.MapAttributeValue; +import org.alfresco.repo.attributes.StringAttributeValue; +import org.alfresco.repo.content.TenantRoutingFileContentStore; +import org.alfresco.repo.dictionary.DictionaryComponent; +import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.repo.node.db.DbNodeServiceImpl; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.site.SiteAVMBootstrap; +import org.alfresco.repo.usage.UserUsageBootstrapJob; +import org.alfresco.repo.usage.UserUsageTrackingComponent; +import org.alfresco.repo.workflow.WorkflowDeployer; +import org.alfresco.service.cmr.admin.RepoAdminService; +import org.alfresco.service.cmr.attributes.AttributeService; +import org.alfresco.service.cmr.module.ModuleService; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.view.RepositoryExporterService; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ParameterCheck; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +/** + * MT Admin Service Implementation. + * + */ + +public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationContextAware +{ + // Logger + private static Log logger = LogFactory.getLog(MultiTAdminServiceImpl.class); + + // Keep hold of the app context + private ApplicationContext ctx; + + // Dependencies + private NodeService nodeService; + private DictionaryComponent dictionaryComponent; + private RepoAdminService repoAdminService; + private AuthenticationComponent authenticationComponent; + private TransactionService transactionService; + private MultiTServiceImpl tenantService; + private AttributeService attributeService; + private PasswordEncoder passwordEncoder; + private TenantRoutingFileContentStore tenantFileContentStore; + private WorkflowService workflowService; + private RepositoryExporterService repositoryExporterService; + private ModuleService moduleService; + private SiteAVMBootstrap siteAVMBootstrap; + private List workflowDeployers = new ArrayList(); + + private String baseAdminUsername = null; + + /* + * 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. + * + * The following PCRE-style + * regex defines a valid label within a FQDN: + * + * ^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$ + * + * Less formally: + * + * o Case insensitive + * o First/last character: alphanumeric + * o Interior characters: alphanumeric plus hyphen + * o Minimum length: 2 characters + * o Maximum length: 63 characters + * + * The FQDN (fully qualified domain name) has the following constraints: + * + * o Maximum 255 characters (***) + * o Must contain at least one alpha + * + * Note: (***) Due to various internal restrictions (such as store identifier) we restrict tenant ids to 75 characters. + */ + + protected final static String REGEX_VALID_DNS_LABEL = "^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$"; + + protected final static String REGEX_CONTAINS_ALPHA = "^(.*)[a-zA-Z](.*)$"; + + protected final static int MAX_LEN = 75; + + public void setNodeService(DbNodeServiceImpl dbNodeService) + { + this.nodeService = dbNodeService; + } + + public void setDictionaryComponent(DictionaryComponent dictionaryComponent) + { + this.dictionaryComponent = dictionaryComponent; + } + + public void setRepoAdminService(RepoAdminService repoAdminService) + { + this.repoAdminService = repoAdminService; + } + + public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) + { + this.authenticationComponent = authenticationComponent; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void setTenantService(MultiTServiceImpl tenantService) + { + this.tenantService = tenantService; + } + + public void setAttributeService(AttributeService attributeService) + { + this.attributeService = attributeService; + } + + public void setPasswordEncoder(PasswordEncoder passwordEncoder) + { + this.passwordEncoder = passwordEncoder; + } + + public void setTenantFileContentStore(TenantRoutingFileContentStore tenantFileContentStore) + { + this.tenantFileContentStore = tenantFileContentStore; + } + + public void setWorkflowService(WorkflowService workflowService) + { + this.workflowService = workflowService; + } + + public void setRepositoryExporterService(RepositoryExporterService repositoryExporterService) + { + this.repositoryExporterService = repositoryExporterService; + } + + /** + * @deprecated see setWorkflowDeployers + */ + public void setWorkflowDeployer(WorkflowDeployer workflowDeployer) + { + // NOOP + logger.warn(WARN_MSG); + } + + public void setModuleService(ModuleService moduleService) + { + this.moduleService = moduleService; + } + + public void setSiteAVMBootstrap(SiteAVMBootstrap siteAVMBootstrap) + { + this.siteAVMBootstrap = siteAVMBootstrap; + } + + public void setBaseAdminUsername(String baseAdminUsername) + { + this.baseAdminUsername = baseAdminUsername; + } + + public static final String PROTOCOL_STORE_USER = "user"; + public static final String PROTOCOL_STORE_WORKSPACE = "workspace"; + public static final String PROTOCOL_STORE_SYSTEM = "system"; + public static final String PROTOCOL_STORE_ARCHIVE = "archive"; + public static final String STORE_BASE_ID_USER = "alfrescoUserStore"; + public static final String STORE_BASE_ID_SYSTEM = "system"; + public static final String STORE_BASE_ID_VERSION1 = "lightWeightVersionStore"; // deprecated + public static final String STORE_BASE_ID_VERSION2 = "version2Store"; + public static final String STORE_BASE_ID_SPACES = "SpacesStore"; + + + private static final String TENANTS_ATTRIBUTE_PATH = "alfresco-tenants"; + private static final String TENANT_ATTRIBUTE_ENABLED = "enabled"; + private static final String TENANT_ROOT_CONTENT_STORE_DIR = "rootContentStoreDir"; + + private List tenantDeployers = new ArrayList(); + + private static final String WARN_MSG = "Please update your alfresco/extension/mt/mt-admin-context.xml to use baseMultiTAdminService (see latest alfresco/extension/mt/mt-admin-context.xml.sample)"; + + protected void checkProperties() + { + if (moduleService == null || siteAVMBootstrap == null || baseAdminUsername == null) + { + logger.warn(WARN_MSG); + } + PropertyCheck.mandatory(this, "NodeService", nodeService); + PropertyCheck.mandatory(this, "DictionaryComponent", dictionaryComponent); + PropertyCheck.mandatory(this, "RepoAdminService", repoAdminService); + PropertyCheck.mandatory(this, "TransactionService", transactionService); + PropertyCheck.mandatory(this, "TenantService", tenantService); + PropertyCheck.mandatory(this, "AttributeService", attributeService); + PropertyCheck.mandatory(this, "PasswordEncoder", passwordEncoder); + PropertyCheck.mandatory(this, "TenantFileContentStore", tenantFileContentStore); + PropertyCheck.mandatory(this, "WorkflowService", workflowService); + PropertyCheck.mandatory(this, "RepositoryExporterService", repositoryExporterService); + PropertyCheck.mandatory(this, "siteAVMBootstrap", siteAVMBootstrap); + PropertyCheck.mandatory(this, "moduleService", moduleService); + PropertyCheck.mandatory(this, "baseAdminUsername", baseAdminUsername); + } + + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + this.ctx = applicationContext; + } + + public void startTenants() + { + checkProperties(); + + AuthenticationUtil.setMtEnabled(true); + + // initialise the tenant admin service and status of tenants (using attribute service) + // note: this requires that the repository schema has already been initialised + + // register dictionary - to allow enable/disable tenant callbacks + register(dictionaryComponent); + + // register file store - to allow enable/disable tenant callbacks + register(tenantFileContentStore); + + UserTransaction userTransaction = transactionService.getUserTransaction(); + + try + { + authenticationComponent.setSystemUserAsCurrentUser(); + userTransaction.begin(); + + // bootstrap Tenant Service internal cache + List tenants = getAllTenants(); + + int enabledCount = 0; + int disabledCount = 0; + + for (Tenant tenant : tenants) + { + if (tenant.isEnabled()) + { + // this will also call tenant deployers registered so far ... + enableTenant(tenant.getTenantDomain(), true); + enabledCount++; + } + else + { + // explicitly disable, without calling disableTenant callback + disableTenant(tenant.getTenantDomain(), false); + disabledCount++; + } + } + + tenantService.register(this); // callback to refresh tenantStatus cache + + userTransaction.commit(); + + if (logger.isInfoEnabled()) + { + logger.info(String.format("Alfresco Multi-Tenant startup - %d enabled tenants, %d disabled tenants", + enabledCount, disabledCount)); + } + } + catch(Throwable e) + { + // rollback the transaction + try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {} + throw new AlfrescoRuntimeException("Failed to bootstrap tenants", e); + } + finally + { + authenticationComponent.clearCurrentSecurityContext(); + } + } + + public void stopTenants() + { + tenantDeployers.clear(); + tenantDeployers = null; + } + + /** + * @see TenantAdminService.createTenant() + */ + public void createTenant(final String tenantDomain, final char[] tenantAdminRawPassword) + { + createTenant(tenantDomain, tenantAdminRawPassword, null); + } + + /** + * @see TenantAdminService.createTenant() + */ + public void createTenant(final String tenantDomain, final char[] tenantAdminRawPassword, String rootContentStoreDir) + { + ParameterCheck.mandatory("tenantAdminRawPassword", tenantAdminRawPassword); + + initTenant(tenantDomain, rootContentStoreDir); + + try + { + // note: runAs would cause auditable property "creator" to be "admin" instead of "System@xxx" + AuthenticationUtil.pushAuthentication(); + AuthenticationUtil.setFullyAuthenticatedUser(getSystemUser(tenantDomain)); + + dictionaryComponent.init(); + 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); + + siteAVMBootstrap.bootstrap(); + + // 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(); + } + finally + { + AuthenticationUtil.popAuthentication(); + } + + logger.info("Tenant created: " + tenantDomain); + } + + /** + * Export tenant - equivalent to the tenant admin running a 'complete repo' export from the Web Client Admin + */ + public void exportTenant(final String tenantDomain, final File directoryDestination) + { + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + repositoryExporterService.export(directoryDestination, tenantDomain); + return null; + } + }, getSystemUser(tenantDomain)); + + logger.info("Tenant exported: " + tenantDomain); + } + + /** + * Create tenant by restoring from a complete repository export. This is equivalent to a bootstrap import using restore-context.xml. + */ + public void importTenant(final String tenantDomain, final File directorySource, String rootContentStoreDir) + { + initTenant(tenantDomain, rootContentStoreDir); + + try + { + // note: runAs would cause auditable property "creator" to be "admin" instead of "System@xxx" + AuthenticationUtil.pushAuthentication(); + AuthenticationUtil.setFullyAuthenticatedUser(getSystemUser(tenantDomain)); + + dictionaryComponent.init(); + tenantFileContentStore.init(); + + // import tenant-specific stores + importBootstrapUserTenantStore(tenantDomain, directorySource); + importBootstrapSystemTenantStore(tenantDomain, directorySource); + importBootstrapVersionTenantStore(tenantDomain, directorySource); + importBootstrapSpacesArchiveTenantStore(tenantDomain, directorySource); + importBootstrapSpacesModelsTenantStore(tenantDomain, directorySource); + importBootstrapSpacesTenantStore(tenantDomain, directorySource); + + // 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(); + } + finally + { + AuthenticationUtil.popAuthentication(); + } + + logger.info("Tenant imported: " + tenantDomain); + } + + public boolean existsTenant(String tenantDomain) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("tenantDomain", tenantDomain); + + return (getTenantAttributes(tenantDomain) != null); + } + + private void putTenantAttributes(String tenantDomain, Tenant tenant) + { + if (! attributeService.exists(TENANTS_ATTRIBUTE_PATH)) + { + // bootstrap + attributeService.setAttribute("", TENANTS_ATTRIBUTE_PATH, new MapAttributeValue()); + } + + MapAttribute tenantProps = new MapAttributeValue(); + tenantProps.put(TENANT_ATTRIBUTE_ENABLED, new BooleanAttributeValue(tenant.isEnabled())); + tenantProps.put(TENANT_ROOT_CONTENT_STORE_DIR, new StringAttributeValue(tenant.getRootContentStoreDir())); + + attributeService.setAttribute(TENANTS_ATTRIBUTE_PATH, tenantDomain, tenantProps); + + // update tenant status cache + ((MultiTServiceImpl)tenantService).putTenant(tenantDomain, tenant); + } + + private Tenant getTenantAttributes(String tenantDomain) + { + if (attributeService.exists(TENANTS_ATTRIBUTE_PATH+"/"+tenantDomain)) + { + MapAttribute map = (MapAttribute)attributeService.getAttribute(TENANTS_ATTRIBUTE_PATH+"/"+tenantDomain); + if (map != null) + { + return new Tenant(tenantDomain, + map.get(TENANT_ATTRIBUTE_ENABLED).getBooleanValue(), + map.get(TENANT_ROOT_CONTENT_STORE_DIR).getStringValue()); + } + } + + return null; + } + + public void enableTenant(String tenantDomain) + { + if (! existsTenant(tenantDomain)) + { + throw new RuntimeException("Tenant does not exist: " + tenantDomain); + } + + if (isEnabledTenant(tenantDomain)) + { + logger.warn("Tenant already enabled: " + tenantDomain); + } + + enableTenant(tenantDomain, true); + } + + private void enableTenant(String tenantDomain, boolean notifyTenantDeployers) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("tenantDomain", tenantDomain); + + Tenant tenant = getTenantAttributes(tenantDomain); + tenant = new Tenant(tenantDomain, true, tenant.getRootContentStoreDir()); // enable + putTenantAttributes(tenantDomain, tenant); + + if (notifyTenantDeployers) + { + // notify listeners that tenant has been enabled + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + for (TenantDeployer tenantDeployer : tenantDeployers) + { + tenantDeployer.onEnableTenant(); + } + return null; + } + }, getSystemUser(tenantDomain)); + } + + logger.info("Tenant enabled: " + tenantDomain); + } + + public void disableTenant(String tenantDomain) + { + if (! existsTenant(tenantDomain)) + { + throw new RuntimeException("Tenant does not exist: " + tenantDomain); + } + + if (! isEnabledTenant(tenantDomain)) + { + logger.warn("Tenant already disabled: " + tenantDomain); + } + + disableTenant(tenantDomain, true); + } + + public void disableTenant(String tenantDomain, boolean notifyTenantDeployers) + { + if (notifyTenantDeployers) + { + // notify listeners that tenant has been disabled + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + for (TenantDeployer tenantDeployer : tenantDeployers) + { + tenantDeployer.onDisableTenant(); + } + return null; + } + }, getSystemUser(tenantDomain)); + } + + // update tenant attributes / tenant cache - need to disable after notifying listeners (else they cannot disable) + Tenant tenant = getTenantAttributes(tenantDomain); + tenant = new Tenant(tenantDomain, false, tenant.getRootContentStoreDir()); // disable + putTenantAttributes(tenantDomain, tenant); + + logger.info("Tenant disabled: " + tenantDomain); + } + + public boolean isEnabledTenant(String tenantDomain) + { + // Check that all the passed values are not null + ParameterCheck.mandatory("tenantDomain", 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); + + Tenant tenant = getTenantAttributes(tenantDomain); + if (tenant != null) + { + return tenant.getRootContentStoreDir(); + } + + return null; + } + + protected void putRootContentStoreDir(String tenantDomain, String rootContentStoreDir) + { + Tenant tenant = getTenantAttributes(tenantDomain); + tenant = new Tenant(tenantDomain, tenant.isEnabled(), rootContentStoreDir); + putTenantAttributes(tenantDomain, tenant); + } + + public Tenant getTenant(String tenantDomain) + { + if (! existsTenant(tenantDomain)) + { + throw new RuntimeException("Tenant does not exist: " + tenantDomain); + } + + return new Tenant(tenantDomain, isEnabledTenant(tenantDomain), getRootContentStoreDir(tenantDomain)); + } + + /** + * @see TenantAdminService.deleteTenant() + */ + public void deleteTenant(String tenantDomain) + { + if (! existsTenant(tenantDomain)) + { + throw new RuntimeException("Tenant does not exist: " + tenantDomain); + } + else + { + try + { + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + List workflowDefs = workflowService.getDefinitions(); + if (workflowDefs != null) + { + for (WorkflowDefinition workflowDef : workflowDefs) + { + workflowService.undeployDefinition(workflowDef.getId()); + } + } + + List messageResourceBundles = repoAdminService.getMessageBundles(); + if (messageResourceBundles != null) + { + for (String messageResourceBundle : messageResourceBundles) + { + repoAdminService.undeployMessageBundle(messageResourceBundle); + } + } + + List models = repoAdminService.getModels(); + if (models != null) + { + for (RepoModelDefinition model : models) + { + repoAdminService.undeployModel(model.getRepoName()); + } + } + + return null; + } + }, getSystemUser(tenantDomain)); + + final String tenantAdminUser = getTenantAdminUser(tenantDomain); + + // delete tenant-specific stores + nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_WORKSPACE, STORE_BASE_ID_SPACES))); + nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_ARCHIVE, STORE_BASE_ID_SPACES))); + nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_WORKSPACE, STORE_BASE_ID_VERSION1))); + nodeService.deleteStore(tenantService.getName(tenantAdminUser, new StoreRef(PROTOCOL_STORE_WORKSPACE, STORE_BASE_ID_VERSION2))); + 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() + { + public Object doWork() + { + for (TenantDeployer tenantDeployer : tenantDeployers) + { + tenantDeployer.onDisableTenant(); + } + return null; + } + }, getSystemUser(tenantDomain)); + + // remove tenant + attributeService.removeAttribute(TENANTS_ATTRIBUTE_PATH, tenantDomain); + } + catch (Throwable t) + { + throw new AlfrescoRuntimeException("Failed to delete tenant: " + tenantDomain, t); + } + } + } + + /** + * @see TenantAdminService.getAllTenants() + */ + public List getAllTenants() + { + MapAttribute map = (MapAttribute)attributeService.getAttribute(TENANTS_ATTRIBUTE_PATH); + + List tenants = new ArrayList(); + + if (map != null) + { + // note: getAllTenants is called first, by TenantDeployer - hence need to initialise the TenantService status cache + Set tenantDomains = map.keySet(); + + for (String tenantDomain : tenantDomains) + { + Tenant tenant = getTenantAttributes(tenantDomain); + tenants.add(new Tenant(tenantDomain, tenant.isEnabled(), tenant.getRootContentStoreDir())); + } + } + + return tenants; // list of tenants or empty list + } + + private void importBootstrapSystemTenantStore(String tenantDomain, File directorySource) + { + // Import Bootstrap (restore) Tenant-Specific Version Store + Properties bootstrapView = new Properties(); + bootstrapView.put("path", "/"); + bootstrapView.put("location", directorySource.getPath()+"/"+tenantDomain+"_system.acp"); + + List bootstrapViews = new ArrayList(1); + bootstrapViews.add(bootstrapView); + + ImporterBootstrap systemImporterBootstrap = (ImporterBootstrap)ctx.getBean("systemBootstrap"); + systemImporterBootstrap.setBootstrapViews(bootstrapViews); + + bootstrapSystemTenantStore(systemImporterBootstrap, tenantDomain); + } + + private void bootstrapSystemTenantStore(ImporterBootstrap systemImporterBootstrap, String tenantDomain) + { + // Bootstrap Tenant-Specific System Store + StoreRef bootstrapStoreRef = systemImporterBootstrap.getStoreRef(); + StoreRef tenantBootstrapStoreRef = new StoreRef(bootstrapStoreRef.getProtocol(), tenantService.getName(bootstrapStoreRef.getIdentifier(), tenantDomain)); + systemImporterBootstrap.setStoreUrl(tenantBootstrapStoreRef.toString()); + + // override default property (workspace://SpacesStore) + List mustNotExistStoreUrls = new ArrayList(); + mustNotExistStoreUrls.add(new StoreRef(PROTOCOL_STORE_WORKSPACE, tenantService.getName(STORE_BASE_ID_USER, tenantDomain)).toString()); + systemImporterBootstrap.setMustNotExistStoreUrls(mustNotExistStoreUrls); + + systemImporterBootstrap.bootstrap(); + + // reset since systemImporter is singleton (hence reused) + systemImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); + + logger.debug("Bootstrapped store: " + tenantService.getBaseName(tenantBootstrapStoreRef)); + } + + private void importBootstrapUserTenantStore(String tenantDomain, File directorySource) + { + // Import Bootstrap (restore) Tenant-Specific User Store + Properties bootstrapView = new Properties(); + bootstrapView.put("path", "/"); + bootstrapView.put("location", directorySource.getPath()+"/"+tenantDomain+"_users.acp"); + + List bootstrapViews = new ArrayList(1); + bootstrapViews.add(bootstrapView); + + ImporterBootstrap userImporterBootstrap = (ImporterBootstrap)ctx.getBean("userBootstrap"); + userImporterBootstrap.setBootstrapViews(bootstrapViews); + + bootstrapUserTenantStore(userImporterBootstrap, tenantDomain, null); + } + + private 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(); + 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)); + } + + userImporterBootstrap.bootstrap(); + + logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); + } + + 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) + { + // 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)); + } + + private void importBootstrapSpacesArchiveTenantStore(String tenantDomain, File directorySource) + { + // Import Bootstrap (restore) Tenant-Specific Spaces Archive Store + 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) + { + // Bootstrap Tenant-Specific Spaces Archive Store + StoreRef bootstrapStoreRef = spacesArchiveImporterBootstrap.getStoreRef(); + bootstrapStoreRef = new StoreRef(bootstrapStoreRef.getProtocol(), tenantService.getName(bootstrapStoreRef.getIdentifier(), tenantDomain)); + spacesArchiveImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); + + // override default property (archive://SpacesStore) + List mustNotExistStoreUrls = new ArrayList(); + mustNotExistStoreUrls.add(bootstrapStoreRef.toString()); + spacesArchiveImporterBootstrap.setMustNotExistStoreUrls(mustNotExistStoreUrls); + + spacesArchiveImporterBootstrap.bootstrap(); + + logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); + } + + private void importBootstrapSpacesModelsTenantStore(String tenantDomain, File directorySource) + { + // Import Bootstrap (restore) Tenant-Specific Spaces Store + Properties bootstrapView = new Properties(); + bootstrapView.put("path", "/"); + bootstrapView.put("location", directorySource.getPath()+"/"+tenantDomain+"_models.acp"); + + List bootstrapViews = new ArrayList(1); + bootstrapViews.add(bootstrapView); + + ImporterBootstrap spacesImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesBootstrap"); + spacesImporterBootstrap.setBootstrapViews(bootstrapViews); + + bootstrapSpacesTenantStore(spacesImporterBootstrap, tenantDomain); + } + + private void importBootstrapSpacesTenantStore(String tenantDomain, File directorySource) + { + // Import Bootstrap (restore) Tenant-Specific Spaces Store + Properties bootstrapView = new Properties(); + bootstrapView.put("path", "/"); + bootstrapView.put("location", directorySource.getPath()+"/"+tenantDomain+"_spaces.acp"); + bootstrapView.put("uuidBinding", "UPDATE_EXISTING"); + + List bootstrapViews = new ArrayList(1); + bootstrapViews.add(bootstrapView); + + ImporterBootstrap spacesImporterBootstrap = (ImporterBootstrap)ctx.getBean("spacesBootstrap"); + spacesImporterBootstrap.setBootstrapViews(bootstrapViews); + + spacesImporterBootstrap.setUseExistingStore(true); + + bootstrapSpacesTenantStore(spacesImporterBootstrap, tenantDomain); + } + + private void bootstrapSpacesTenantStore(ImporterBootstrap spacesImporterBootstrap, String tenantDomain) + { + // Bootstrap Tenant-Specific Spaces Store + StoreRef bootstrapStoreRef = spacesImporterBootstrap.getStoreRef(); + bootstrapStoreRef = new StoreRef(bootstrapStoreRef.getProtocol(), tenantService.getName(bootstrapStoreRef.getIdentifier(), tenantDomain)); + spacesImporterBootstrap.setStoreUrl(bootstrapStoreRef.toString()); + + // override admin username property + Properties props = spacesImporterBootstrap.getConfiguration(); + props.put("alfresco_user_store.adminusername", getTenantAdminUser(tenantDomain)); + + // override guest username property + props.put("alfresco_user_store.guestusername", getTenantGuestUser(tenantDomain)); + + spacesImporterBootstrap.bootstrap(); + + // calculate any missing usages + UserUsageTrackingComponent userUsageTrackingComponent = (UserUsageTrackingComponent)ctx.getBean(UserUsageBootstrapJob.KEY_COMPONENT); + userUsageTrackingComponent.bootstrapInternal(); + + logger.debug("Bootstrapped store: " + tenantService.getBaseName(bootstrapStoreRef)); + } + + public void deployTenants(final TenantDeployer deployer, Log logger) + { + 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(); + authenticationComponent.setSystemUserAsCurrentUser(); + + List tenants = null; + try + { + userTransaction.begin(); + tenants = getAllTenants(); + userTransaction.commit(); + } + catch(Throwable e) + { + // rollback the transaction + try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {} + throw new AlfrescoRuntimeException("Failed to get tenants", e); + } + finally + { + authenticationComponent.clearCurrentSecurityContext(); + } + + for (Tenant tenant : tenants) + { + if (tenant.isEnabled()) + { + try + { + // deploy within context of tenant domain + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + // init the service within tenant context + deployer.init(); + return null; + } + }, getSystemUser(tenant.getTenantDomain())); + + } + catch (Throwable e) + { + logger.error("Deployment failed" + e); + + StringWriter stringWriter = new StringWriter(); + e.printStackTrace(new PrintWriter(stringWriter)); + logger.error(stringWriter.toString()); + + // tenant deploy failure should not necessarily affect other tenants + } + } + } + } + } + + public void undeployTenants(final TenantDeployer deployer, Log logger) + { + 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(); + authenticationComponent.setSystemUserAsCurrentUser(); + + List tenants = null; + try + { + userTransaction.begin(); + tenants = getAllTenants(); + userTransaction.commit(); + } + catch(Throwable e) + { + // rollback the transaction + try { if (userTransaction != null) {userTransaction.rollback();} } catch (Exception ex) {} + try {authenticationComponent.clearCurrentSecurityContext(); } catch (Exception ex) {} + throw new AlfrescoRuntimeException("Failed to get tenants", e); + } + + try + { + AuthenticationUtil.pushAuthentication(); + for (Tenant tenant : tenants) + { + if (tenant.isEnabled()) + { + try + { + // undeploy within context of tenant domain + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + // destroy the service within tenant context + deployer.destroy(); + return null; + } + }, getSystemUser(tenant.getTenantDomain())); + + } + catch (Throwable e) + { + logger.error("Undeployment failed" + e); + + StringWriter stringWriter = new StringWriter(); + e.printStackTrace(new PrintWriter(stringWriter)); + logger.error(stringWriter.toString()); + + // tenant undeploy failure should not necessarily affect other tenants + } + } + } + } + finally + { + AuthenticationUtil.popAuthentication(); + } + } + } + + public void register(TenantDeployer deployer) + { + if (deployer == null) + { + throw new AlfrescoRuntimeException("TenantDeployer must be provided"); + } + + if (! tenantDeployers.contains(deployer)) + { + tenantDeployers.add(deployer); + } + } + + public void unregister(TenantDeployer deployer) + { + if (deployer == null) + { + throw new AlfrescoRuntimeException("TenantDeployer must be provided"); + } + + if (tenantDeployers != null) + { + tenantDeployers.remove(deployer); + } + } + + public void register(WorkflowDeployer workflowDeployer) + { + if (workflowDeployer == null) + { + throw new AlfrescoRuntimeException("WorkflowDeployer must be provided"); + } + + if (! workflowDeployers.contains(workflowDeployer)) + { + workflowDeployers.add(workflowDeployer); + } + } + + public void resetCache(String tenantDomain) + { + if (existsTenant(tenantDomain)) + { + if (isEnabledTenant(tenantDomain)) + { + enableTenant(tenantDomain); + } + else + { + disableTenant(tenantDomain); + } + } + else + { + throw new AlfrescoRuntimeException("No such tenant " + tenantDomain); + } + } + + private void initTenant(String tenantDomain, String rootContentStoreDir) + { + validateTenantName(tenantDomain); + + if (existsTenant(tenantDomain)) + { + throw new AlfrescoRuntimeException("Tenant already exists: " + tenantDomain); + } + + if (rootContentStoreDir == null) + { + rootContentStoreDir = tenantFileContentStore.getDefaultRootDir(); + } + else + { + File tenantRootDir = new File(rootContentStoreDir); + if ((tenantRootDir.exists()) && (tenantRootDir.list().length != 0)) + { + throw new AlfrescoRuntimeException("Tenant root directory is not empty: " + rootContentStoreDir); + } + } + + // init - need to enable tenant (including tenant service) before stores bootstrap + Tenant tenant = new Tenant(tenantDomain, true, rootContentStoreDir); + putTenantAttributes(tenantDomain, tenant); + } + + private void validateTenantName(String tenantDomain) + { + ParameterCheck.mandatory("tenantDomain", tenantDomain); + + if (tenantDomain.length() > MAX_LEN) + { + throw new IllegalArgumentException(tenantDomain + " is not a valid tenant name (must be less than " + MAX_LEN + " characters)"); + } + + if (! Pattern.matches(REGEX_CONTAINS_ALPHA, tenantDomain)) + { + throw new IllegalArgumentException(tenantDomain + " is not a valid tenant name (must contain at least one alpha character)"); + } + + String[] dnsLabels = tenantDomain.split("\\."); + if (dnsLabels.length != 0) + { + for (int i = 0; i < dnsLabels.length; i++) + { + if (! Pattern.matches(REGEX_VALID_DNS_LABEL, dnsLabels[i])) + { + throw new IllegalArgumentException(dnsLabels[i] + " is not a valid DNS label (must match " + REGEX_VALID_DNS_LABEL + ")"); + } + } + } + else + { + if (! Pattern.matches(REGEX_VALID_DNS_LABEL, tenantDomain)) + { + throw new IllegalArgumentException(tenantDomain + " is not a valid DNS label (must match " + REGEX_VALID_DNS_LABEL + ")"); + } + } + } + + // tenant deployer/user services delegated to tenant service + + public boolean isEnabled() + { + return tenantService.isEnabled(); + } + + public String getCurrentUserDomain() + { + return tenantService.getCurrentUserDomain(); + } + + public String getUserDomain(String username) + { + return tenantService.getUserDomain(username); + } + + public String getBaseNameUser(String username) + { + return tenantService.getBaseNameUser(username); + } + + public String getDomainUser(String baseUsername, String tenantDomain) + { + return tenantService.getDomainUser(baseUsername, tenantDomain); + } + + public String getDomain(String name) + { + return tenantService.getDomain(name); + } + + // local helpers + + public String getBaseAdminUser() + { + // default for backwards compatibility only - eg. upgrade of existing MT instance (mt-admin-context.xml.sample) + if (baseAdminUsername != null) + { + return baseAdminUsername; + } + return AuthenticationUtil.getAdminUserName(); + } + + private String getSystemUser(String tenantDomain) + { + return tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain); + } + + private String getTenantAdminUser(String tenantDomain) + { + + return tenantService.getDomainUser(getBaseAdminUser(), tenantDomain); + } + + private String getTenantGuestUser(String tenantDomain) + { + return tenantService.getDomainUser(authenticationComponent.getGuestUserName(), tenantDomain); + } +} diff --git a/source/java/org/alfresco/repo/version/Version2ServiceImpl.java b/source/java/org/alfresco/repo/version/Version2ServiceImpl.java index 0eaa33ee87..cc948cc05d 100644 --- a/source/java/org/alfresco/repo/version/Version2ServiceImpl.java +++ b/source/java/org/alfresco/repo/version/Version2ServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2008 Alfresco Software Limited. + * Copyright (C) 2005-2009 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -27,12 +27,13 @@ package org.alfresco.repo.version; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.TreeMap; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; @@ -40,6 +41,7 @@ import org.alfresco.repo.policy.PolicyScope; import org.alfresco.repo.version.common.VersionHistoryImpl; import org.alfresco.repo.version.common.VersionImpl; import org.alfresco.repo.version.common.VersionUtil; +import org.alfresco.repo.version.common.VersionHistoryImpl.VersionComparatorAsc; import org.alfresco.service.cmr.repository.AspectMissingException; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; @@ -67,6 +69,8 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe private PermissionService permissionService; + private static Comparator versionComparatorAsc = new VersionComparatorAsc(); + public void setPermissionService(PermissionService permissionService) { this.permissionService = permissionService; @@ -607,27 +611,25 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe VersionHistory versionHistory = null; List versionsAssoc = this.dbNodeService.getChildAssocs(versionHistoryRef, Version2Model.CHILD_QNAME_VERSIONS, RegexQNamePattern.MATCH_ALL); - - Map versionHistoryMap = new HashMap(versionsAssoc.size()); - for (ChildAssociationRef versionAssoc : versionsAssoc) - { - String localName = versionAssoc.getQName().getLocalName(); - if (localName.indexOf(Version2Model.CHILD_VERSIONS+"-") != -1) // TODO - could remove this belts-and-braces, should match correctly above ! - { - int versionNumber = Integer.parseInt(localName.substring((Version2Model.CHILD_VERSIONS+"-").length())); - versionHistoryMap.put(versionNumber, versionAssoc.getChildRef()); - } - } - - Map sortedMap = new TreeMap(versionHistoryMap); + + List versions = new ArrayList(versionsAssoc.size()); + + for (ChildAssociationRef versionAssoc : versionsAssoc) + { + String localName = versionAssoc.getQName().getLocalName(); + if (localName.indexOf(Version2Model.CHILD_VERSIONS+"-") != -1) // TODO - could remove this belts-and-braces, should match correctly above ! + { + versions.add(getVersion(versionAssoc.getChildRef())); + } + } + + Collections.sort(versions, versionComparatorAsc); // Build the version history object boolean isRoot = true; Version preceeding = null; - for (NodeRef versionRef : sortedMap.values()) + for (Version version : versions) { - Version version = getVersion(versionRef); - if (isRoot == true) { versionHistory = new VersionHistoryImpl(version); diff --git a/source/java/org/alfresco/repo/version/VersionMigrator.java b/source/java/org/alfresco/repo/version/VersionMigrator.java index 76f7d42b9c..7ac61c1146 100644 --- a/source/java/org/alfresco/repo/version/VersionMigrator.java +++ b/source/java/org/alfresco/repo/version/VersionMigrator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2008 Alfresco Software Limited. + * Copyright (C) 2005-2009 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/source/java/org/alfresco/repo/version/VersionMigratorTest.java b/source/java/org/alfresco/repo/version/VersionMigratorTest.java index 649c0b3aa2..51d9d210bf 100644 --- a/source/java/org/alfresco/repo/version/VersionMigratorTest.java +++ b/source/java/org/alfresco/repo/version/VersionMigratorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2009 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -26,14 +26,19 @@ package org.alfresco.repo.version; import java.io.Serializable; import java.util.Date; +import java.util.HashMap; import java.util.Map; import java.util.Set; import org.alfresco.model.ContentModel; import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.version.common.counter.VersionCounterService; +import org.alfresco.service.cmr.coci.CheckOutCheckInService; import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.version.Version; import org.alfresco.service.cmr.version.VersionHistory; import org.alfresco.service.namespace.QName; @@ -55,6 +60,8 @@ public class VersionMigratorTest extends BaseVersionStoreTest protected VersionMigrator versionMigrator; protected PolicyComponent policyComponent; protected DictionaryService dictionaryService; + protected CheckOutCheckInService cociService; + protected VersionCounterService versionCounterService; public VersionMigratorTest() { @@ -71,6 +78,9 @@ public class VersionMigratorTest extends BaseVersionStoreTest this.version2Service = (Version2ServiceImpl)applicationContext.getBean("versionService"); this.versionNodeService = (NodeService)applicationContext.getBean("versionNodeService"); // note: auto-switches between V1 and V2 + this.cociService = (CheckOutCheckInService)applicationContext.getBean("CheckoutCheckinService"); + this.versionCounterService = (VersionCounterService)applicationContext.getBean("versionCounterService"); + // Version1Service is used to create the version nodes in Version1Store (workspace://lightWeightVersionStore) version1Service.setDbNodeService(dbNodeService); version1Service.setNodeService(dbNodeService); @@ -243,4 +253,100 @@ public class VersionMigratorTest extends BaseVersionStoreTest logger.info("testMigrateMultipleVersions: Migrated from oldVHNodeRef = " + oldVHNodeRef + " to newVHNodeRef = " + newVHNodeRef); } + + public void test_ETHREEOH_1540() throws Exception + { + // Create the node used for tests + NodeRef nodeRef = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}MyVersionableNode"), + TEST_TYPE_QNAME, + this.nodeProperties).getChildRef(); + + nodeService.addAspect(nodeRef, ContentModel.ASPECT_TITLED, null); + nodeService.setProperty(nodeRef, ContentModel.PROP_NAME, "name"); + + // Add the initial content to the node + ContentWriter contentWriter = this.contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); + contentWriter.setMimetype("text/plain"); + contentWriter.setEncoding("UTF-8"); + contentWriter.putContent("my content"); + + VersionHistory vh1 = version1Service.getVersionHistory(nodeRef); + assertNull(vh1); + + version2Service.useDeprecatedV1 = true; + + // note: for testing only - not recommended !!! + versionCounterService.setVersionNumber(new StoreRef(StoreRef.PROTOCOL_WORKSPACE, VersionModel.STORE_ID), 100); + + // Add the version aspect to the created node + nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, null); + + vh1 = version1Service.getVersionHistory(nodeRef); + assertNull(vh1); + + NodeRef workingCopyNodeRef = cociService.checkout(nodeRef); + + vh1 = version1Service.getVersionHistory(nodeRef); + assertNull(vh1); + + int v1count = 3; + + for (int i = 1; i <= v1count; i++) + { + Map versionProperties = new HashMap(); + versionProperties.put(Version.PROP_DESCRIPTION, "This is a test checkin - " + i); + + cociService.checkin(workingCopyNodeRef, versionProperties); + + vh1 = version1Service.getVersionHistory(nodeRef); + assertEquals(i, vh1.getAllVersions().size()); + + workingCopyNodeRef = cociService.checkout(nodeRef); + + vh1 = version1Service.getVersionHistory(nodeRef); + assertEquals(i, vh1.getAllVersions().size()); + } + + NodeRef oldVHNodeRef = version1Service.getVersionHistoryNodeRef(nodeRef); + + version2Service.useDeprecatedV1 = false; + + // Migrate and delete old version history ! + NodeRef versionedNodeRef = versionMigrator.v1GetVersionedNodeRef(oldVHNodeRef); + + //int nextVersionNumber = versionCounterService.nextVersionNumber(new StoreRef(StoreRef.PROTOCOL_WORKSPACE, VersionModel.STORE_ID)); + //versionCounterService.setVersionNumber(new StoreRef(StoreRef.PROTOCOL_WORKSPACE, Version2Model.STORE_ID), nextVersionNumber); + + // to force the error: https://issues.alfresco.com/jira/browse/ETHREEOH-1540 + versionCounterService.setVersionNumber(new StoreRef(StoreRef.PROTOCOL_WORKSPACE, Version2Model.STORE_ID), 0); + + NodeRef newVHNodeRef = versionMigrator.migrateVersionHistory(oldVHNodeRef, versionedNodeRef); + versionMigrator.v1DeleteVersionHistory(oldVHNodeRef); + + VersionHistory vh2 = version2Service.getVersionHistory(nodeRef); + assertEquals(v1count, vh2.getAllVersions().size()); + + int v2count = 3; + + for (int i = 1; i <= v2count; i++) + { + versionProperties = new HashMap(); + versionProperties.put(Version.PROP_DESCRIPTION, "This is a test checkin - " + (v1count + i)); + + cociService.checkin(workingCopyNodeRef, versionProperties); + + vh2 = version2Service.getVersionHistory(nodeRef); + assertEquals((v1count + i), vh2.getAllVersions().size()); + + workingCopyNodeRef = cociService.checkout(nodeRef); + + vh2 = version2Service.getVersionHistory(nodeRef); + assertEquals((v1count + i), vh2.getAllVersions().size()); + } + + logger.info("testMigrateOneCheckoutVersion: Migrated from oldVHNodeRef = " + oldVHNodeRef + " to newVHNodeRef = " + newVHNodeRef); + } } diff --git a/source/java/org/alfresco/repo/version/common/VersionHistoryImpl.java b/source/java/org/alfresco/repo/version/common/VersionHistoryImpl.java index e69cd5970d..8158e1bd9c 100644 --- a/source/java/org/alfresco/repo/version/common/VersionHistoryImpl.java +++ b/source/java/org/alfresco/repo/version/common/VersionHistoryImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2009 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -36,6 +36,7 @@ import org.alfresco.service.cmr.version.Version; import org.alfresco.service.cmr.version.VersionDoesNotExistException; import org.alfresco.service.cmr.version.VersionHistory; import org.alfresco.service.cmr.version.VersionServiceException; +import org.alfresco.util.VersionNumber; /** * Version History implementation. @@ -72,7 +73,7 @@ public class VersionHistoryImpl implements VersionHistory /* * Versions ordered by creation date (descending) */ - private static VersionComparator versionComparator = new VersionComparator(); + private static Comparator versionComparatorDesc = new VersionComparatorDesc(); private List versions = new ArrayList(); /** @@ -124,7 +125,7 @@ public class VersionHistoryImpl implements VersionHistory */ public Collection getAllVersions() { - Collections.sort(versions, versionComparator); + Collections.sort(versions, versionComparatorDesc); return versions; } @@ -223,13 +224,38 @@ public class VersionHistoryImpl implements VersionHistory * * Note: Descending create date order */ - public static class VersionComparator implements Comparator, Serializable + public static class VersionComparatorDesc implements Comparator, Serializable { private static final long serialVersionUID = 6227528170880231770L; public int compare(Version v1, Version v2) { - return v2.getCreatedDate().compareTo(v1.getCreatedDate()); + int result = v2.getCreatedDate().compareTo(v1.getCreatedDate()); + if (result == 0) + { + result = new VersionNumber(v2.getVersionLabel()).compareTo(new VersionNumber(v1.getVersionLabel())); + } + return result; + } + } + + /** + * Version Comparator + * + * Note: Ascending create date order + */ + public static class VersionComparatorAsc implements Comparator, Serializable + { + private static final long serialVersionUID = 6227528170880231770L; + + public int compare(Version v1, Version v2) + { + int result = v1.getCreatedDate().compareTo(v2.getCreatedDate()); + if (result == 0) + { + result = new VersionNumber(v1.getVersionLabel()).compareTo(new VersionNumber(v2.getVersionLabel())); + } + return result; } } diff --git a/source/test-resources/tenant/mt-admin-context.xml b/source/test-resources/tenant/mt-admin-context.xml index 81bece177d..b9f4d270f0 100644 --- a/source/test-resources/tenant/mt-admin-context.xml +++ b/source/test-resources/tenant/mt-admin-context.xml @@ -3,6 +3,17 @@ + + + + + + + +