From b2f9df29d1c659631f081555aa7f2bcc297d1d47 Mon Sep 17 00:00:00 2001 From: Britt Park Date: Wed, 8 Nov 2006 05:17:40 +0000 Subject: [PATCH] Humongous merge. It is incomplete, however; faces-config-navigation.xml and ClientConfigElement were both beyond me, and are just the raw conflict merge data. If Kev can't figure out how they should go together by tomorrow AM (for me) I'll dig back in. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/WCM-DEV2/root@4306 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/application-context.xml | 72 +- config/alfresco/auditConfig.xml | 401 +- config/alfresco/bootstrap-context.xml | 10 +- config/alfresco/bootstrap/categories.xml | 96 +- .../bootstrap/example_javascripts.acp | Bin 11208 -> 4221 bytes config/alfresco/bootstrap/file_plan.xml | 828 ---- config/alfresco/content-services-context.xml | 494 +-- config/alfresco/core-services-context.xml | 1344 +++--- .../post-create-indexes.sql | 3 + .../post-create-indexes.sql | 30 + .../post-create-indexes.sql | 30 + .../AlfrescoSchemaUpdate-1.4-1.sql | 85 +- .../AlfrescoSchemaUpdate-1.4-2.sql | 9 +- .../AlfrescoSchemaUpdate-1.4-1.sql | 92 + .../AlfrescoSchemaUpdate-1.4-2.sql | 69 + config/alfresco/desktop/Alfresco.exe | Bin 53248 -> 327680 bytes config/alfresco/desktop/showDetails.js | 26 + config/alfresco/desktop/urlLink.js | 29 + .../alfresco/domain/hibernate-cfg.properties | 29 +- config/alfresco/ehcache-default.xml | 366 +- .../custom-connection-pool-context.xml.sample | 57 + .../custom-db-connection.properties.sample | 8 +- ...custom-hibernate-dialect.properties.sample | 5 + .../extension/file-servers-custom.xml | 55 + .../index-tracking-context.xml.sample | 71 + config/alfresco/file-servers.xml | 27 +- config/alfresco/hibernate-context.xml | 552 +-- config/alfresco/import-export-context.xml | 7 +- .../alfresco/messages/bpm-messages.properties | 4 +- .../alfresco/messages/copy-service.properties | 3 + .../messages/patch-service.properties | 12 +- .../messages/schema-update.properties | 2 + .../workflow-interpreter-help.properties | 1 + .../messages/workflow-interpreter-help.txt | 174 + config/alfresco/model/contentModel.xml | 1422 +++---- config/alfresco/network-protocol-context.xml | 1 + config/alfresco/public-services-context.xml | 2380 +++++------ .../public-services-security-context.xml | 4 +- config/alfresco/repository.properties | 275 +- config/alfresco/rule-services-context.xml | 3 + config/alfresco/script-services-context.xml | 29 + .../templates/content/examples/show_audit.ftl | 173 + .../templates/content_template_examples.xml | 10 +- config/alfresco/version.properties | 2 +- config/alfresco/workflow-context.xml | 33 +- .../CAlfrescoApp/source/alfresco/Alfresco.cpp | 2 +- .../java/org/alfresco/filesys/CIFSServer.java | 50 +- .../java/org/alfresco/filesys/FTPServer.java | 51 +- .../alfresco/filesys/ftp/FTPSrvSession.java | 2 +- .../server/auth/AlfrescoAuthenticator.java | 193 +- .../server/auth/CifsAuthenticator.java | 2 +- .../filesys/server/auth/ClientInfo.java | 40 + .../auth/EnterpriseCifsAuthenticator.java | 2 +- .../auth/ntlm/AlfrescoAuthenticator.java | 427 -- .../server/auth/ntlm/NTLMLogonDetails.java | 17 + .../server/config/ServerConfiguration.java | 34 +- .../smb/server/repo/ContentDiskDriver.java | 64 +- .../server/repo/ContentIOControlHandler.java | 79 + .../smb/server/repo/DesktopAction.java | 85 +- .../smb/server/repo/DesktopParams.java | 14 + .../repo/desk/JavaScriptDesktopAction.java | 5 + .../server/repo/desk/URLDesktopAction.java | 28 +- .../server/repo/pseudo/MemoryNetworkFile.java | 13 +- .../server/repo/pseudo/PseudoNetworkFile.java | 13 +- .../jcr/exporter/JCRSystemXMLExporter.java | 2 +- .../java/org/alfresco/model/ContentModel.java | 536 +-- .../action/executer/CopyActionExecuter.java | 2 +- .../repo/admin/patch/AbstractPatch.java | 7 +- .../repo/admin/patch/PatchExecuter.java | 23 +- .../impl/AbstractPermissionChangePatch.java | 169 + .../patch/impl/ActionRuleDecouplingPatch.java | 14 +- .../patch/impl/ContentPermissionPatch.java | 41 +- .../admin/patch/impl/PermissionDataPatch.java | 41 +- .../patch/impl/UniqueChildNamePatch.java | 76 +- .../impl/UpdateGuestPermissionPatch.java | 27 +- .../admin/patch/util/ImportFileUpdater.java | 573 +++ .../repo/content/RoutingContentService.java | 1012 ++--- .../content/cleanup/ContentStoreCleaner.java | 498 +-- .../alfresco/repo/copy/CopyServiceImpl.java | 43 +- .../repo/copy/CopyServiceImplTest.java | 27 + .../descriptor/DescriptorServiceImpl.java | 76 +- .../repo/descriptor/DescriptorStartupLog.java | 159 +- .../repo/dictionary/DictionaryDAOTest.java | 24 +- .../alfresco/repo/dictionary/TestModel.java | 2 + .../dictionary/dictionarydaotest_model.xml | 27 + .../repo/domain/hibernate/ChildAssocImpl.java | 8 +- .../repo/domain/hibernate/Permission.hbm.xml | 10 + .../PermissionsDaoComponentImpl.java | 1200 +++--- .../repo/domain/hibernate/Transaction.hbm.xml | 33 +- .../repo/domain/schema/SchemaBootstrap.java | 1166 +++--- .../repo/importer/ImporterBootstrap.java | 45 +- .../repo/importer/ImporterBootstrapViews.java | 62 + .../system/SystemExporterImporter.java | 12 +- .../importer/system/SystemInfoBootstrap.java | 23 +- .../org/alfresco/repo/jscript/Actions.java | 162 +- .../jscript/BaseScriptImplementation.java | 57 + .../alfresco/repo/jscript/CategoryNode.java | 206 + .../repo/jscript/CategoryTemplateNode.java | 202 + .../alfresco/repo/jscript/Classification.java | 144 + .../java/org/alfresco/repo/jscript/Node.java | 44 +- .../repo/jscript/RhinoScriptService.java | 51 +- .../alfresco/repo/jscript/ScriptLogger.java | 2 +- .../alfresco/repo/jscript/ScriptUtils.java | 56 + .../org/alfresco/repo/jscript/Session.java | 79 + .../filefolder/FileFolderServiceImpl.java | 57 +- .../repo/node/db/DbNodeServiceImpl.java | 3640 ++++++++-------- .../alfresco/repo/node/db/NodeDaoService.java | 11 +- .../HibernateNodeDaoServiceImpl.java | 2381 +++++------ .../node/index/AbstractReindexComponent.java | 235 ++ .../index/FullIndexRecoveryComponent.java | 202 +- .../index/IndexRemoteTransactionTracker.java | 119 + .../IndexRemoteTransactionTrackerTest.java | 125 + .../org/alfresco/repo/rule/BaseRuleTest.java | 3 +- .../repo/rule/RuleServiceCoverageTest.java | 3 +- .../alfresco/repo/rule/RuleServiceImpl.java | 180 +- .../repo/rule/RuleServiceImplTest.java | 70 +- .../impl/lucene/LuceneSearcherImpl2.java | 34 +- .../search/impl/lucene/index/IndexInfo.java | 10 +- .../ldap/LDAPGroupExportSource.java | 1580 +++---- .../ldap/LDAPPersonExportSource.java | 684 +-- .../dynamic/LockOwnerDynamicAuthority.java | 12 +- .../impl/PermissionServiceImpl.java | 2264 +++++----- .../impl/PermissionServiceTest.java | 3678 ++++++++--------- .../permissions/impl/acegi/ACLEntryVoter.java | 48 +- .../impl/model/PermissionModel.java | 1932 ++++----- .../security/person/PersonServiceImpl.java | 776 ++-- .../repo/template/Classification.java | 123 + .../repo/template/FreeMarkerProcessor.java | 7 + .../org/alfresco/repo/template/Session.java | 50 + .../common/counter/VersionCounterService.java | 12 + .../repo/workflow/WorkflowDeployer.java | 23 +- .../repo/workflow/WorkflowInterpreter.java | 707 ++++ .../repo/workflow/WorkflowPackageImpl.java | 9 + .../repo/workflow/jbpm/JBPMEngine.java | 23 +- .../repo/workflow/jbpm/JBPMEngineTest.java | 24 +- .../service/cmr/action/ActionService.java | 4 +- .../service/cmr/repository/CopyService.java | 26 +- .../cmr/repository/ScriptImplementation.java | 20 + .../service/cmr/repository/ScriptService.java | 8 + .../service/cmr/repository/TemplateNode.java | 51 +- 140 files changed, 20060 insertions(+), 16456 deletions(-) delete mode 100644 config/alfresco/bootstrap/file_plan.xml create mode 100644 config/alfresco/dbscripts/create/1.4/org.hibernate.dialect.Dialect/post-create-indexes.sql create mode 100644 config/alfresco/dbscripts/create/1.4/org.hibernate.dialect.Oracle9Dialect/post-create-indexes.sql create mode 100644 config/alfresco/dbscripts/create/1.4/org.hibernate.dialect.SQLServerDialect/post-create-indexes.sql create mode 100644 config/alfresco/dbscripts/upgrade/1.4/org.hibernate.dialect.Oracle9Dialect/AlfrescoSchemaUpdate-1.4-1.sql create mode 100644 config/alfresco/dbscripts/upgrade/1.4/org.hibernate.dialect.Oracle9Dialect/AlfrescoSchemaUpdate-1.4-2.sql create mode 100644 config/alfresco/desktop/showDetails.js create mode 100644 config/alfresco/desktop/urlLink.js create mode 100644 config/alfresco/extension/custom-connection-pool-context.xml.sample create mode 100644 config/alfresco/extension/file-servers-custom.xml create mode 100644 config/alfresco/extension/index-tracking-context.xml.sample create mode 100644 config/alfresco/messages/copy-service.properties create mode 100644 config/alfresco/messages/workflow-interpreter-help.properties create mode 100644 config/alfresco/messages/workflow-interpreter-help.txt create mode 100644 config/alfresco/templates/content/examples/show_audit.ftl delete mode 100644 source/java/org/alfresco/filesys/server/auth/ntlm/AlfrescoAuthenticator.java create mode 100644 source/java/org/alfresco/repo/admin/patch/impl/AbstractPermissionChangePatch.java create mode 100644 source/java/org/alfresco/repo/admin/patch/util/ImportFileUpdater.java create mode 100644 source/java/org/alfresco/repo/importer/ImporterBootstrapViews.java create mode 100644 source/java/org/alfresco/repo/jscript/BaseScriptImplementation.java create mode 100644 source/java/org/alfresco/repo/jscript/CategoryNode.java create mode 100644 source/java/org/alfresco/repo/jscript/CategoryTemplateNode.java create mode 100644 source/java/org/alfresco/repo/jscript/Classification.java create mode 100644 source/java/org/alfresco/repo/jscript/ScriptUtils.java create mode 100644 source/java/org/alfresco/repo/jscript/Session.java create mode 100644 source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTracker.java create mode 100644 source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTrackerTest.java create mode 100644 source/java/org/alfresco/repo/template/Classification.java create mode 100644 source/java/org/alfresco/repo/template/Session.java create mode 100644 source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java create mode 100644 source/java/org/alfresco/service/cmr/repository/ScriptImplementation.java diff --git a/config/alfresco/application-context.xml b/config/alfresco/application-context.xml index 422b2108a3..c16c0ee3e8 100644 --- a/config/alfresco/application-context.xml +++ b/config/alfresco/application-context.xml @@ -1,36 +1,36 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/auditConfig.xml b/config/alfresco/auditConfig.xml index 1478d733f1..f639baff74 100644 --- a/config/alfresco/auditConfig.xml +++ b/config/alfresco/auditConfig.xml @@ -1,202 +1,201 @@ - - - - - - - - - - - - - - - false - false - false - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + false + false + false + false + false + false + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 1b1eb69b2b..5354175164 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -35,7 +35,7 @@ - classpath:alfresco/dbscripts/create/1.4/${db.script.dialect}/sample.sql + classpath:alfresco/dbscripts/create/1.4/${db.script.dialect}/post-create-indexes.sql @@ -149,10 +149,6 @@ /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.scripts.childname} alfresco/bootstrap/example_javascripts.acp - - /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.childname} - alfresco/bootstrap/file_plan.xml - @@ -192,6 +188,10 @@ + + + + diff --git a/config/alfresco/bootstrap/categories.xml b/config/alfresco/bootstrap/categories.xml index 87012bd9cc..5e33800a75 100644 --- a/config/alfresco/bootstrap/categories.xml +++ b/config/alfresco/bootstrap/categories.xml @@ -1098,102 +1098,10 @@ + - + -Records Categoriesrm:recordCategory-root - - Review Periodrm:reviewPeriod-root - - Nonerm:reviewPeriod-0 - TBDrm:reviewPeriod-1 - Annuallyrm:reviewPeriod-2 - Calendar Year Endrm:reviewPeriod-3 - Fiscal Year Endrm:reviewPeriod-4 - Semi-Annuallyrm:reviewPeriod-5 - Quarterlyrm:reviewPeriod-6 - Monthlyrm:reviewPeriod-7 - Weeklyrm:reviewPeriod-8 - Dailyrm:reviewPeriod-9 - - - - Media Typesrm:mediaTypes-root - - TBDrm:mediaTypes-0 - Electronicrm:mediaTypes-1 - Paperrm:mediaTypes-2 - Microformrm:mediaTypes-3 - Portablerm:mediaTypes-4 - - - - Markingsrm:markings-root - - NONErm:markings-0 - - Classificationsrm:markings-1 - - UNCLASSIFIEDrm:markings-1-1 - RESTRICTEDrm:markings-1-2 - CONFIDENTIALrm:markings-1-3 - SECRETrm:markings-1-4 - TOP SECRETrm:markings-1-5 - - - - Handlingrm:markings-2 - - COMSECrm:markings-2-1 - RDrm:markings-2-2 - FRDrm:markings-2-3 - SPECAT-A SIOP-ESIrm:markings-2-4 - SPECAT-Brm:markings-2-5 - US-UK EYES ONLYrm:markings-2-6 - ATOMALrm:markings-2-7 - EXCLUSIVErm:markings-2-8 - CRYPTOrm:markings-2-9 - TRCrm:markings-2-10 - FOR OFFICIAL USE ONLYrm:markings-2-11 - SBUrm:markings-2-12 - DEA SENSITIVErm:markings-2-13 - DOD UCNIrm:markings-2-14 - EXDISrm:markings-2-15 - LIMDISrm:markings-2-16 - NODISrm:markings-2-17 - SOSUSrm:markings-2-18 - EYES ONLYrm:markings-2-19 - - - - Disseminationrm:markings-3 - - ORCONrm:markings-3-1 - PROPINrm:markings-3-2 - NOFORNrm:markings-3-3 - NOCONTRACTrm:markings-3-34 - FOUOrm:markings-3-5 - RELTOrm:markings-3-6 - EYES ONLYrm:markings-3-7 - - - - Reasonrm:markings-4 - - CLASSrm:markings-4-1 - RSNrm:markings-4-2 - DERVrm:markings-4-3 - DNGrm:markings-4-4 - DECLrm:markings-4-5 - - - - - - - - - diff --git a/config/alfresco/bootstrap/example_javascripts.acp b/config/alfresco/bootstrap/example_javascripts.acp index 48bf6636e4f47c1ff5bc6656b99c777e45bc24a2..4aea5d40480fb825735c096515b4132f9c3c1ed1 100644 GIT binary patch delta 1205 zcmV;m1WNnJSN$LkP)h>@3IG5A007ToQZhGO|A+Q3^lg7l|+K1w05x%-w}0Q#c-9Vg@M(L z52JrLoEh>nL3s6gAB7|ZrMZaL6L02D2*gYTJl?J+@9%z`zM8!DU#wCN56kR@pgz|2 z<9f0)MlK!a;o)IM!;J#X#7wAvt&=@<6iA`D5$fP%!}>1>$%l!+(1046SxOu1rBLSP z;L{-D@ycl#yTb3O0e|kgms58!_2zf()$-+H>CI>J%f&aYw{+cNh+5tq1k{H-Z{M3; z%v|zS0O{A6Usus}{%zwsRLUh2k)-kA&z*?C8UoG?dQf$+Uo}%)8f}Mvt$l4VoK7`X zEW!a}1FNhc{o&gfImM5|Mti3Zh54NkfgdqZP#&~#A2?R@` z3=(-6NG@18ZFMzBO%=a*f3Lzd?5Q2>o~AVSAsZfo{-e;iSl!2nBcBcu*3CV)pVmH) zn*R;!#nt6w^|N7rHC9d?wbKv(CZG3hG4IOWE%nck@|e66RGU`SYhjFQ!mri?igHuc zm~I#9<)Q`SHYbgLJ$-7k+GZXjqDMrJi2k>TF8N|Wn}Sf!j@LgFH4qz;QpH80$#qd| za{c=o!e|)kfw}GV;P!m}t=9|OWkPq@)B*ARiM2Aad}R5^@-tz1an7W1Kti#7b{Nl( zt2t)NA`r8~gE(F+Sc5^{4$Sd+4~~28Wj~JJv>LiauK{U)-d-&u$VZTmAU_k3;}QVe zmQ2Wl;@jQxJCR@K1^BKk0LjF}2t0c34H1g`boa|!3*w>As=HM!7N}yo>O_v0DYgzITXB@f2+#7m z#qxdV8}gTbJ&3-zSo9-$Iq$a#1N?=`Qyqlo%_TF!e1!Q3^D}|Dz5P^R5*6chahSlD z0sSnI{Vq?B<_VIG5)pnW=sG1>MSHP=f2)WxtmD#7Ll_#|iyq+iyjQ)zT_pIyrdM2- z*WKyRxYP6hoXCYJd-4APP)h>@6aWAK2mq!{tTc_Y5e0z>4gmrb015yA0002bVp27e zJ}5&O+hS5RBphZU>jMA)|04hZ8~^|S000000F#<1PXb&7li?{MlRyOolMyK@0)0qK+ADI=3rDlP(14U^$1A{sR=YI9Ia1qJ{B T000O82mpNo008O@00000!T>cs delta 8111 zcmZ{JWmFu@vhCmmcXxMp3BfJ6yKC^lZ5UjGli(iQCAe#FcYAcKcXzH{Z=_tx$G zXRlseyZTr4uC5n~5gjyDMHpBlz~8m)G0&5T#*KDlMaJvZ9}|}d3jj>>B&yJW9G%zr zFou8LXa$z>QpRoX zhtNyfcQY}=%6^u zReW+?&1Ba4Sa1Y^cm}O}45IM|^$dDg=Ey)Jv6bzZ zX)GRM9ChzA6t8B=1}>;RgyU#$5mOZy>KQT1R=tK?t34H=Pw5V6m?rA9e`$L$T;j!S za+Y#HaDc@f+{`O5P1%|lQI8HtEQX!2MTLZG`bnM~^q|wObdA{4y->7zy9n*~d5s@k ziL1Pfdo6rJFnYmKRc+HnL8|YL15r7I^;pLuVxxWC{hL45I^(&oDC7Lib=G{INjlos%JG>L@Nf*dnrbS~de0 zMfHb#I-#5ZVFIvL>tWM4$)1&N;fcQYTkQbf3aYUweu#An__^UKs;SM3aKT?6m*e(s zy&gmkq(A=2L7GeQvkJubt0tWF8<_|2eG*XTZuqG!JHX(?t2R&Yv8PVcpj>#^uqMi~ zldLkC_W`>>5cwA5mYAiwmi%#1<7kDYI5PlN3gHbP)Phf43<>2_^9{SV?5Xhn;dr{L z#tQD*QE`)--O{k!bixOaTDb4Bh2#-cJx2WQc63d`{ujo~;Qhad0Itgjs9^3cO`+{B zxGtDC<3W|RzK^tkTg>Be7CyKyg&U6e?JE2^m!A%Fqc7_bNN}gE2=IBs17(mQJJj@A z(xo}=(e3j?_Q+HgKx_e;VmB6NNp`AO!Z6Q-9n|hZasKsC)^T#5-jzAa*`t`4E5u9u znpBbQQsfQK6yC;GygGeX-M!ByTkCkYDxRchx(N@DQlAMMgeV$zI3^~_AeDF{mz@@% zf^G+(WFU`|>`mKwOj6LFFt;ktzUw$zY*Vr~1R`>L|DFr2$>`SDQyEGxk}ZZ?FV+I7 z{e&?AJ9YCVx;mgps}Y|$)F!>R&3F|V-YCRx{lMPpz8tru0$ywlQhs{1vD`eq!FvcE z`HP4TmQbNFH!<^1*Nm?YSMG-(o3L?dR=0oxW){i?yd3I`PY+z}_qR4@=74CV@at;b zj{LLT-^r6*A zTdm+59)gy~k5ERUYn^{@?sRU!7F8a1I4vN!>n*>ATdx;z zah~DdwG>rnjVoTE|E*ogU^JozUs$(yL@=;8Z&eHt(5gwzOOsiZEQNvpK6lyFc?VT;pTpGk0~L0L>fg9Kf^nE z)f8Cpij1z>C9{%5(bZ>T7-CjOrxwl??gC2Nwk_Mt|0?|uT}0St7pr}*6U@*dOJq}d zY(xY7;X1a;MTRGLfNKu^@`ju9UKx z+}JIc8>{f)0OciyRpTan-HOG7B76ohKr}lLxsOEv6H{`@A#uY@`SVN#&+GtQJUK`- z&^<;_VHs6UZW;v@{?L|7jSk8VX>=LdQP&t+{g!aUtXAsd!NUZqk~5M%e<(~Dk4h47 z)U1GS3(?4|mI;dxLDyZjf%Y-m1)`~|JiFyCE?yH_#J{3W`7U%i%6Ksn&@oh}55v*$ z4YTMguG~E}SbRx3M~2X<%wGs+7z<=~b3a2qizBCi=J_KTg$DodWF^>@Ck5G^PgM5i zDgS_eC}RyVN7Ro`Jud^X1Boc)7$C|`&fDsDYl5~pJmRXiUJO3z@d&gie;rQpsC?lhKPvaoi zOw$^K)Sg-a1bOk#7EE52Vq`d6XVyyA(YA5d`b4{_;9my8_{<-Z(?P=lQG;PjOp=6x zQqTzm%|G|cT77wjsd7(a`Lf6g=Mt|nUQwrYOyD}UTQ?Yv)wk2|g?<$;Ns}mcUqu%= z^KI#G?Fck8eJS?H>%?csH*w2HnjlH?oQ%RG!h3ypiwRv+8VGqn$UiM!j*P7|6}N{$ zZ@DT-DLXP4J4!A$%>ZFJMK*^P(`2zinXu^P3@Bvqe$LaJ1nNb*hQm@ao6#&ZAuZlw zO<_}Eq+u29_8p~Vx1Y|!LLN*lce~8K2wlkamQj#;ryOk4RmL6`m0r8t^xES}+Y{P{ z&QH5BGnPiv%w=00Ee5;!u~U9p=jigmg=wxlG+c;KpQD@8y#NvDZJmq27^eYjzhY6v z16&rVW@^fdU>>89ptO`Jjs=DzhwqF-jhX)YkIKy_g9Gxexq)ku`6# zcK0&AkeAnUWWV zxEZTBsWqJoD%i@$s~%bVqx+FIsY_!7nk)$@Y3oCkq4{vyavsA$2W-hPW^MH-_Xc5cYV?{I8)%|C&y3?EexV05C+-lhH4CUwbSMMV_BWAWHQJZ@M6FZ+bt&BDi_ zgZ{2tQJ?8cok7FA=)ALl+sR7S$8*cJU_Y zc*ubECn*|rf7oiZCuTIFP%P|Xm3}DV#sx)i3TGj$t1_-uAfIDd_tUYMO>0|IQnEJ> z%L17e4+}$!CL)}*O^s>s0^hd+KIy$kqnnhyR_>LuXep7tT&gz% zzuFP=hi2~K`@&{Lfb!@ClzSKkV ziAO~yD(?#K4!K=A4n&5eApkW9%CE6UI`a`@V zXo6bq#I#qLUakttv*mQ={s<3~yocB?23zYS2QEd(cc1nb{k$<%m#)+Sg%Cv>P5Ah( zMu@6NCS;?m$`qMT2W7DTZsfDVkL}oh`cr-v;ZfNYO{V$#k!TaBb?XDlQ8>T$SCw9( zIAEatrS?O-QZPJq`Xo=g{GgXFCuSdfo!!ScIY_qAmN?#phqP+pp0q zI>KWn+8$)BMAfh9i-Pr;D$^|9rCFT*6W2q`&Cw)AbQJv@zTxAIW*v}QHe>%dK3a6l zkA;5bft-{|uEj$%Wo0y1pDllPH;${{kEBztJ z=ke9?Oh=Oc-u$KfQx@BcecYCDw|;~1Qq5wJJi;xO*-ead1d(&Bjy9!>qm*+P+`51Vc4_U7>K}Bg( z>27Q2+~j!DI0l`)V{{0CdFba82tv-}Mrc16SChb}Pgei>KAxoc_@)!64Z0Li?KiOZ zyx)zOx)vW!U@fur8Baab@koJq{>G@fq&29*hK_KXuzQu!d?80hbBoC~O$-=%I`#E5 zVsU*1{k7x&vf$xz)Vv61X zi;u4n(&(nVN;^H%=|iFE82X5kbJLCE94QeE?#<|y*@x#TrWCKjettccxUYl=yf!62)VMph5}~L; zV8Z?aLL;!jJO`zota6V}fh#Mw+hlB{ZW48=iY-jwEU}#L^UVKu$WyeeA8`&TQ()RWCC83jbLCa^F^^vGn zi)gX1F&?+R&*Wmh&qryDa(g&lBiksTU{l!P;ZUe^>n3hNaPhc;g!rAyusRGUxy@Xe zTnV$DM(8cdEQ`F1c0VW7D{M7kAL1%QQJAoq^(DT}6}ORXJz`AMhjy$S<%!s@JOGk5 ztKrB2WV|_|r2S8ydz~7@qh47!SA|ArM-bCjOJH8+xx(VfUmWuR?Z5#>MU-=pVf@jG z6cfQRoGTv*_}+f&=9s#Kb-)Gzfih8u^8R}gCJKzeh;w#}-=h|R6B8@`IWqZx1*R}U z%M)S<{bY?1hT#YcFYFlyh^){iRX0J+24jpN^Ln0yHO0`y3U;X*e&%_;5Gim>FpgvI zf{jeyL`?(5djJxs>0tQVp!Wh)-HJ6!lyD_{k5>r9`22jU*nVq}c2C!qs>3C{lq%C& z((R(Ho3RUi$D8h6;d<(5zph{u=eMew9Qm$WR|a-6l(wyL4*rivU%8xxL~*zr2Q9p` zum4T~`TG}~4q0)G191J1M7c?P7Ng0%EOKe?#DSkIH5hB3tgXXpr=?XuY&pwW?)W~W z^`##=q&!Vf28pci;;Hs8_c1yg4KN?7`wjQhLL>7%TvV?`hCE!52sV2wtc)KFLlIGP zS4!C$+u!@k2GK)$pEk?3hwO7A!*PJA%S&f$_Jbon7Ud4@k64ah1@pVrL4d_(vUOnqy-G z{AP*;1)4=yYE`hOu!4QTej4^ThFb0bdfs;#8*S?iF0HdxF=t!h_7{15Wbeb+CHk7@ zDv14RKTP{h7#Uow%r!FPxtP}Y=RT4-eqTpe{iaTNKV4q8<6D#hTBt9{(WgdYznRm; zzFI6&Ri-0rTSHHEc1%^+5{zpuo?Ej2q5c~m7$IRoG3eiSS?aqn*pAtyGpjqt1QHdB zfg5g?37N!;30o7k-U*C?G$c(EjpRSqVKCClkr!~&VU?e`O>u~-O^ExRjZRT6j zDkSAA0&VUW#D!7`V$gsP^uPDWA7G71TP{}~QQgk?aej7ya+uhvXWxJL^5mc%4^hLO z9Of$Ia)`k~QK&L&YJLjeyB(9bLdwS`kQh+jC{25Z)Yt5J*SV25-eRNTH{BC=+mfyK ztXxwDr?lX?oKLpmOq%W)A8IVedXy+v7d2UGv^b9#pV~18B60Y%XfVJuVeLGgw#p`I zW1pcZyJ_Xjh89F9KiRu z&B3l;ZRU)r&aZQns~E!QTA5zhYp3u)`$*zlK_0FyRJN>gt@-PjHt*0oH zEiv#n-K5_x>RPY!AjLWIIK4iINi4(d8tcXOOZ1HqXa*nXnhnlD@0-biF0?;O_Y+iy z%1hIJQnOWpq+kl57esDd@d)a!QT=49cp^i7;@?RqwzD?DuinJI5Bwn-V)c1lcNKW) zb3FUB6HK4sWhcZw-y`2d8P#pHOY= zf8wVTpOu1y1y@j8laopFPWN6Irg@po%&u!ei~&uDX-yGpHa5a$DzE$lt@iXxv%kLv zFdwW7CwmD+vF(uz>-HNxE!b*$;hJO;$jJkBgFgG1>6|@`Ojk9@KH8H@`E?&2_XNl< zUl?v#LeSy8$z7}1vmPDkgN5!kWx>3NyurnMhyrAM%H9}!)D=!~g@LXypI&N6TWEbi zF_O29{;XZ#Eipmky&9zGDG6d%7AXC-^m}2N=Vuxsspb+uv7Jkq7)h z(ta%cP3aT|b=LoJ|2j)$nR8rI>}r7!A~LS2RK!xH*LgT=P_WJD7il&WGBYR}_h#YI31t$#q_N7S|hh6Y@BgE%hUBO%%JSFE;kwQul-V{e;o zYyWKeh#|R82rUc!n1`&_LtLKLzj2nyflAee7$2qse`dg=UpCR0t@;mKP|>iSoWH{T zk55Q@`>KmD@AEH$0|1Cg0RWc&^;PF!XKiKSW8q-+<`nkzHdKgZF+j3Aps$;f<}jR2 ztCs)?+%Qc_1sJ4DRbC(6pA#!3IbJH?{XEyBUpu+|O#BXl3cGO1Qdy8MCMSoTtplHW z|4dFo@OsevB}|H;{39i9bOK{tl41Ky-=~9eqnD@5iD08lPzOM%rF`bmM6e*No49Zm zwWKaxm{JVbqE_Dq1sBUETqX^gL;zidq<4)q*881VhGRLDXUYz;c=RnAG>!SLU}oHm zVmVjz$>Ln*wV#`-oBEMB=DeuXS4RXQ7l@F%Z6s6zIBOZ@m{t$#;(C3K`ffAEA~1k{ z#hqx>uJ}_lV;Lw(kAyrGPu)fvUYfNgH8 za@c%+T$dFDrAdC_NBVdP;mnw>Tn1uF?u1%~nnZfi&;h7#oC?+V8&|zhez|<8ihP!3 zN`5*=9_C?2vNSM@T{u^zSa19`RlRF3vdYm}Z3o^8t9uLx%WfRRz2_r&A2U`sSyXcn zl~9qMu{)A0?Rt5ALajAe%+a8}GToMuppnS355}*xG0!gm__OIVC6`xLCH7H%Vz{%h z9loe@LI%C!bLkRqZnk+)?ea>S_Dc+plrT~9m@Ppqr`@u7M{M<0duiNcg?_oYu1+Dn z-2y)9xfwZGGH1v1lLYOCSEWzWry;<9Mx-s&>Gxrel#fLxsu|=1Qn2rS2H^A=xr~q= z?}=)8t(cy=?YHnZ(mJ1hF2B!CfV!3F?Nn3!{1bFk(67C^l||R$Z5Q;U7^1nT+!r^l z8YyS4&)hjK*mI7PfQ_z|8Pq@(F5y-Pn>#uE8Y0YsV2Zk-N&7Ve@%92u&bgcF1q{Bk zLN&Q6&PEdaid`koc$~(r#5g2ll1RZ$mxzw@zJo9XTgOZ1GqQz;z8l7rWU!B3C_;)7 zl_)4BXz)iC?z<8SuEeB)jK;V+%dQ?ZDZPp`QfTZ)GR?gzb%wHxk#R0YT#3|ir3NX# zisD`t#^NC0Uhx7Ls3=UL{BU5W5rE7NHEwbq^dWNfynf`>GvyGK^5`Ds(utix4x1 zX-Ey}GE7fAkyXJgo?boJ;3lDU_6jb-`6E+Hpsun?E{3|q``V};e z0r!Bn4y{IC;L!LXM20W6Z=<`lXKnP&@nVTjZ{o^2&Yps6y@{JV41B1SaJr4A8;$(f z4aJMFi_hwg4Yg_WI@z;X!WegQzZ#TQH!pB~8&H9mZ?D0{-w$(|r7Ts)GB? zea2Yh!}pClbIM=4Lz31fE;(rmF~2jK)g?{f0=d-E1OCjIgE>ssBx}Nu4I}g#(`zSu zW9Q+jzWLPyasmdozr*?UIJ~=PeugP45KdI;Y^}sr4)oe~F=gcWnQS z5MdqL<}PKoFK+)U%%@F5>KJ?MJ%S?7*-y&oKW#SIR0{o5QrLH2FXEWWzkp?rc1A;I z1RP%w1gKqhe3%@0CjE%(hh8hyFG!bPZx_8B#5C@HUWhxxZrw0;Gj{Eiv^!~jD0T^O z^>js~?+TFI0?n?p8k6ZRx?v(ap3~*y{&^qd%=x|3)4_YZY^qgBZN(C#-H zkMa-2#9)KEOa6et$n-by|1}8uSHgegOF(-IBo_XE#zOzP`2X3({v+_Ocx - - - admin - 2006-09-02T09:54:43.796+01:00 - 1.4.0 (Dev @build-number@) - /app:company_home/app:dictionary/app:space_templates/cm:File_x0020_Plan - - - - - - - - - - - - - false - N/A - ISF - /app:company_home/app:dictionary/app:content_templates/cm:records_report.ftl - space-icon-cd - 15cc634c-3a5c-11db-ae95-09a9ea11d246 - false - GRS 1 item 1 - /cm:generalclassifiable/cm:Records_x0020_Categories/cm:Review_x0020_Period/cm:Quarterly - /cm:generalclassifiable/cm:Records_x0020_Categories/cm:Media_x0020_Types/cm:Electronic - 1 - false - 2006-09-02T09:21:51.750+01:00 - false - NARA - 1.0 - admin - false - false - false - Obsolete or Superseded - File Plan - SpacesStore - /cm:generalclassifiable/cm:Records_x0020_Categories/cm:Review_x0020_Period/cm:Quarterly - File Plan Template for Records Management - 458 - File Plan Template - false - 5.0 - false - /cm:generalclassifiable/cm:Records_x0020_Categories/cm:Markings/cm:NONE - admin - - false - 2006-09-02T09:53:17.750+01:00 - Destroy/delete when superseded by a like survey or study, or when no longer needed, whichever is later. - workspace - 0000-00 - - Defense Information Systems Agency - false - 2.0 - - - - - - - - - - admin - 2006-09-02T09:41:28.718+01:00 - 7836ac9c-3a5d-11db-ae95-09a9ea11d246 - admin - 459 - workspace - 7836ac9c-3a5d-11db-ae95-09a9ea11d246 - SpacesStore - 2006-09-02T09:31:46.093+01:00 - - - - - - - - - - - Add Email Aspect - 783b678e-3a5d-11db-ae95-09a9ea11d246 - 460 - Email Handling - 2006-09-02T09:31:46.109+01:00 - admin - false - 2006-09-02T09:31:46.250+01:00 - true - admin - workspace - false - - - inbound - - - 783b678e-3a5d-11db-ae95-09a9ea11d246 - SpacesStore - - - - - - - - - - false - - - - 78343b97-3a5d-11db-ae95-09a9ea11d246 - 461 - 2006-09-02T09:31:46.140+01:00 - admin - 2006-09-02T09:31:46.250+01:00 - - - - admin - composite-action - workspace - 78343b97-3a5d-11db-ae95-09a9ea11d246 - SpacesStore - - - - - - - - - - admin - 2006-09-02T09:31:46.250+01:00 - 78343b98-3a5d-11db-ae95-09a9ea11d246 - admin - 462 - compare-mime-type - workspace - false - 78343b98-3a5d-11db-ae95-09a9ea11d246 - SpacesStore - 2006-09-02T09:31:46.171+01:00 - - - - - - - - - - - message/rfc822 - - admin - value - 2006-09-02T09:31:46.250+01:00 - 78474e6f-3a5d-11db-ae95-09a9ea11d246 - admin - 463 - workspace - 78474e6f-3a5d-11db-ae95-09a9ea11d246 - SpacesStore - 2006-09-02T09:31:46.187+01:00 - - - - - - - - - - - - - - - false - - - - 78343b99-3a5d-11db-ae95-09a9ea11d246 - 464 - 2006-09-02T09:31:46.203+01:00 - admin - 2006-09-02T09:31:46.250+01:00 - - - - admin - add-features - workspace - 78343b99-3a5d-11db-ae95-09a9ea11d246 - SpacesStore - - - - - - - - - - - {http://www.alfresco.org/model/content/1.0}emailed - - admin - aspect-name - 2006-09-02T09:31:46.250+01:00 - 784c0960-3a5d-11db-ae95-09a9ea11d246 - admin - 465 - workspace - 784c0960-3a5d-11db-ae95-09a9ea11d246 - SpacesStore - 2006-09-02T09:31:46.234+01:00 - - - - - - - - - - - - - - - - - - - - Add record aspect and set up record data - d091637d-3a5d-11db-ae95-09a9ea11d246 - 466 - Record Set-up - 2006-09-02T09:34:14.312+01:00 - admin - false - 2006-09-02T09:34:14.546+01:00 - true - admin - workspace - false - - - inbound - - - d091637d-3a5d-11db-ae95-09a9ea11d246 - SpacesStore - - - - - - - - - - false - - - - d08ca886-3a5d-11db-ae95-09a9ea11d246 - 467 - 2006-09-02T09:34:14.343+01:00 - admin - 2006-09-02T09:34:14.546+01:00 - - - - admin - composite-action - workspace - d08ca886-3a5d-11db-ae95-09a9ea11d246 - SpacesStore - - - - - - - - - - admin - 2006-09-02T09:34:14.546+01:00 - d08ca887-3a5d-11db-ae95-09a9ea11d246 - admin - 468 - is-subtype - workspace - false - d08ca887-3a5d-11db-ae95-09a9ea11d246 - SpacesStore - 2006-09-02T09:34:14.375+01:00 - - - - - - - - - - - {http://www.alfresco.org/model/content/1.0}content - - admin - type - 2006-09-02T09:34:14.546+01:00 - d09d4a5f-3a5d-11db-ae95-09a9ea11d246 - admin - 469 - workspace - d09d4a5f-3a5d-11db-ae95-09a9ea11d246 - SpacesStore - 2006-09-02T09:34:14.390+01:00 - - - - - - - - - - - - - - - false - - - - d08ca888-3a5d-11db-ae95-09a9ea11d246 - 470 - 2006-09-02T09:34:14.421+01:00 - admin - 2006-09-02T09:34:14.546+01:00 - - - - admin - add-features - workspace - d08ca888-3a5d-11db-ae95-09a9ea11d246 - SpacesStore - - - - - - - - - - - {http://www.alfresco.org/model/record/1.0}record - - admin - aspect-name - 2006-09-02T09:34:14.546+01:00 - d0a47550-3a5d-11db-ae95-09a9ea11d246 - admin - 471 - workspace - d0a47550-3a5d-11db-ae95-09a9ea11d246 - SpacesStore - 2006-09-02T09:34:14.453+01:00 - - - - - - - - - - - - - false - - - - d08ca889-3a5d-11db-ae95-09a9ea11d246 - 472 - 2006-09-02T09:34:14.484+01:00 - admin - 2006-09-02T09:34:14.546+01:00 - - - - admin - extract-metadata - workspace - d08ca889-3a5d-11db-ae95-09a9ea11d246 - SpacesStore - - - - - - - - - - false - - - - d08ca88a-3a5d-11db-ae95-09a9ea11d246 - 473 - 2006-09-02T09:34:14.515+01:00 - admin - 2006-09-02T09:34:14.546+01:00 - - - - admin - script - workspace - d08ca88a-3a5d-11db-ae95-09a9ea11d246 - SpacesStore - - - - - - - - - - - /app:company_home/app:dictionary/app:scripts/cm:record_setup.js - - admin - script-ref - 2006-09-02T09:34:14.546+01:00 - d0b2cd31-3a5d-11db-ae95-09a9ea11d246 - admin - 474 - workspace - d0b2cd31-3a5d-11db-ae95-09a9ea11d246 - SpacesStore - 2006-09-02T09:34:14.531+01:00 - - - - - - - - - - - - - - - - - - - - Set up record folder - 96342c65-3a5e-11db-ae95-09a9ea11d246 - 475 - Records Folder - 2006-09-02T09:39:45.906+01:00 - admin - false - 2006-09-02T09:39:46.093+01:00 - true - admin - workspace - false - - - inbound - - - 96342c65-3a5e-11db-ae95-09a9ea11d246 - SpacesStore - - - - - - - - - - false - - - - 9631e26f-3a5e-11db-ae95-09a9ea11d246 - 476 - 2006-09-02T09:39:45.921+01:00 - admin - 2006-09-02T09:39:46.093+01:00 - - - - admin - composite-action - workspace - 9631e26f-3a5e-11db-ae95-09a9ea11d246 - SpacesStore - - - - - - - - - - admin - 2006-09-02T09:39:46.093+01:00 - 9631e270-3a5e-11db-ae95-09a9ea11d246 - admin - 477 - is-subtype - workspace - false - 9631e270-3a5e-11db-ae95-09a9ea11d246 - SpacesStore - 2006-09-02T09:39:45.953+01:00 - - - - - - - - - - - {http://www.alfresco.org/model/content/1.0}folder - - admin - type - 2006-09-02T09:39:46.093+01:00 - 96428447-3a5e-11db-ae95-09a9ea11d246 - admin - 478 - workspace - 96428447-3a5e-11db-ae95-09a9ea11d246 - SpacesStore - 2006-09-02T09:39:45.984+01:00 - - - - - - - - - - - - - - - false - - - - 9631e271-3a5e-11db-ae95-09a9ea11d246 - 479 - 2006-09-02T09:39:46.000+01:00 - admin - 2006-09-02T09:39:46.093+01:00 - - - - admin - add-features - workspace - 9631e271-3a5e-11db-ae95-09a9ea11d246 - SpacesStore - - - - - - - - - - - {http://www.alfresco.org/model/record/1.0}record - - admin - aspect-name - 2006-09-02T09:39:46.093+01:00 - 9649b038-3a5e-11db-ae95-09a9ea11d246 - admin - 480 - workspace - 9649b038-3a5e-11db-ae95-09a9ea11d246 - SpacesStore - 2006-09-02T09:39:46.031+01:00 - - - - - - - - - - - - - false - - - - 9631e272-3a5e-11db-ae95-09a9ea11d246 - 481 - 2006-09-02T09:39:46.046+01:00 - admin - 2006-09-02T09:39:46.093+01:00 - - - - admin - script - workspace - 9631e272-3a5e-11db-ae95-09a9ea11d246 - SpacesStore - - - - - - - - - - - /app:company_home/app:dictionary/app:scripts/cm:record_folder.js - - admin - script-ref - 2006-09-02T09:39:46.093+01:00 - 964e6b29-3a5e-11db-ae95-09a9ea11d246 - admin - 482 - workspace - 964e6b29-3a5e-11db-ae95-09a9ea11d246 - SpacesStore - 2006-09-02T09:39:46.078+01:00 - - - - - - - - - - - - - - - - - - - - Process record lifecycle based upon the file plan - d36b6897-3a5e-11db-ae95-09a9ea11d246 - 483 - Record Lifecycle - 2006-09-02T09:41:28.609+01:00 - admin - false - 2006-09-02T09:41:28.718+01:00 - true - admin - workspace - false - - - update - - - d36b6897-3a5e-11db-ae95-09a9ea11d246 - SpacesStore - - - - - - - - - - false - - - - d36b6892-3a5e-11db-ae95-09a9ea11d246 - 484 - 2006-09-02T09:41:28.640+01:00 - admin - 2006-09-02T09:41:28.734+01:00 - - - - admin - composite-action - workspace - d36b6892-3a5e-11db-ae95-09a9ea11d246 - SpacesStore - - - - - - - - - - admin - 2006-09-02T09:41:28.734+01:00 - d36b6893-3a5e-11db-ae95-09a9ea11d246 - admin - 485 - no-condition - workspace - false - d36b6893-3a5e-11db-ae95-09a9ea11d246 - SpacesStore - 2006-09-02T09:41:28.656+01:00 - - - - - - - - - - - - false - - - - d36b6894-3a5e-11db-ae95-09a9ea11d246 - 486 - 2006-09-02T09:41:28.687+01:00 - admin - 2006-09-02T09:41:28.734+01:00 - - - - admin - script - workspace - d36b6894-3a5e-11db-ae95-09a9ea11d246 - SpacesStore - - - - - - - - - - - /app:company_home/app:dictionary/app:scripts/cm:record_lifecycle.js - - admin - script-ref - 2006-09-02T09:41:28.734+01:00 - d37c3179-3a5e-11db-ae95-09a9ea11d246 - admin - 487 - workspace - d37c3179-3a5e-11db-ae95-09a9ea11d246 - SpacesStore - 2006-09-02T09:41:28.703+01:00 - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/config/alfresco/content-services-context.xml b/config/alfresco/content-services-context.xml index 97c936a8b5..a230559c59 100644 --- a/config/alfresco/content-services-context.xml +++ b/config/alfresco/content-services-context.xml @@ -1,247 +1,247 @@ - - - - - - - - ${dir.contentstore} - - - - - - - ${dir.contentstore.deleted} - - - - - - - - - - - - - - - - - - - - - - - - 14 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - classpath:alfresco/mimetype/mimetype-map.xml - classpath:alfresco/mimetype/mimetype-map-openoffice.xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - application/pdf - text/plain - - - - - - - - - - - - application/msword - text/plain - - - - - - - - - - - - - classpath:alfresco/mimetype/openoffice-document-formats.xml - - - - - - - - - - - - - application/pdf - - - - - - - - - - - - - imconvert "${source}" ${options} "${target}" - - - convert ${source} ${options} ${target} - - - - - - - - - - - - - + + + + + + + + ${dir.contentstore} + + + + + + + ${dir.contentstore.deleted} + + + + + + + + + + + + + + + + + + + + + + + + 14 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + classpath:alfresco/mimetype/mimetype-map.xml + classpath:alfresco/mimetype/mimetype-map-openoffice.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + application/pdf + text/plain + + + + + + + + + + + + application/msword + text/plain + + + + + + + + + + + + + classpath:alfresco/mimetype/openoffice-document-formats.xml + + + + + + + + + + + + + application/pdf + + + + + + + + + + + + + imconvert "${source}" ${options} "${target}" + + + convert ${source} ${options} ${target} + + + + + + + + + + + + + diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index c8740bc99e..47e209f688 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -1,672 +1,672 @@ - - - - - - - - - - - - - - true - - - - classpath:alfresco/repository.properties - classpath:alfresco/version.properties - classpath:alfresco/domain/transaction.properties - - - - - - - - ${db.driver} - - - ${db.url} - - - ${db.username} - - - ${db.password} - - - ${db.pool.initial} - - - ${db.pool.max} - - - ${db.pool.maxIdleTime} - - - 1 - - - - - - - - - - ${server.transaction.allow-writes} - - - - - - - - - - - alfresco.messages.system-messages - alfresco.messages.dictionary-messages - alfresco.messages.version-service - alfresco.messages.permissions-service - alfresco.messages.content-service - alfresco.messages.coci-service - alfresco.messages.template-service - alfresco.messages.lock-service - alfresco.messages.patch-service - alfresco.messages.schema-update - alfresco.messages.webdav-messages - - - - - - - - - - - - ${mail.host} - - - ${mail.port} - - - ${mail.username} - - - ${mail.password} - - - ${mail.encoding} - - - - - - - - - - org.alfresco.repo.search.Indexer - - - - - - - - - - - - - - indexerComponent - - - - - - - - - - - - - - - - org.alfresco.repo.search.IndexerAndSearcher - - - - - - - - - - - - - - - - - - - - - - org.alfresco.service.cmr.search.CategoryService - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ${dir.indexes} - - - - - - - - - ${lucene.maxAtomicTransformationTime} - - - ${lucene.query.maxClauses} - - - ${lucene.indexer.batchSize} - - - ${lucene.indexer.minMergeDocs} - - - ${lucene.indexer.mergeFactor} - - - ${lucene.indexer.maxMergeDocs} - - - ${dir.indexes.lock} - - - ${lucene.indexer.maxFieldLength} - - - ${lucene.write.lock.timeout} - - - ${lucene.commit.lock.timeout} - - - ${lucene.lock.poll.interval} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - org.alfresco.repo.version.common.counter.VersionCounterService - - - - - - - - - - ${server.transaction.mode.default}, PROPAGATION_REQUIRES_NEW - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - alfresco/model/dictionaryModel.xml - alfresco/model/systemModel.xml - alfresco/model/contentModel.xml - alfresco/model/bpmModel.xml - alfresco/model/wcmModel.xml - alfresco/model/applicationModel.xml - alfresco/model/forumModel.xml - alfresco/model/recordsModel.xml - - - org/alfresco/repo/security/authentication/userModel.xml - org/alfresco/repo/action/actionModel.xml - org/alfresco/repo/rule/ruleModel.xml - org/alfresco/repo/version/version_model.xml - - - - - alfresco/model/dataTypeAnalyzers - alfresco/messages/system-model - alfresco/messages/dictionary-model - alfresco/messages/content-model - alfresco/messages/bpm-messages - alfresco/messages/application-model - alfresco/messages/forum-model - - - - - - - - alfresco/model/defaultCustomModel.xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - - - - - - - - - - - - - ${dir.root}/backup-lucene-indexes - - - - - - - - - - 5 - - - 20 - - - 60 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - org.alfresco.repo.configuration.ConfigurableService - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - + + + + + + + + + + + + + + true + + + + classpath:alfresco/repository.properties + classpath:alfresco/version.properties + classpath:alfresco/domain/transaction.properties + + + + + + + + ${db.driver} + + + ${db.url} + + + ${db.username} + + + ${db.password} + + + ${db.pool.initial} + + + ${db.pool.max} + + + false + + + + + + + + + + ${server.transaction.allow-writes} + + + + + + + + + + + alfresco.messages.system-messages + alfresco.messages.dictionary-messages + alfresco.messages.version-service + alfresco.messages.permissions-service + alfresco.messages.content-service + alfresco.messages.coci-service + alfresco.messages.template-service + alfresco.messages.lock-service + alfresco.messages.patch-service + alfresco.messages.schema-update + alfresco.messages.webdav-messages + alfresco.messages.copy-service + + + + + + + + + + + + ${mail.host} + + + ${mail.port} + + + ${mail.username} + + + ${mail.password} + + + ${mail.encoding} + + + + + + + + + + org.alfresco.repo.search.Indexer + + + + + + + + + + + + + + indexerComponent + + + + + + + + + + + + + + + + org.alfresco.repo.search.IndexerAndSearcher + + + + + + + + + + + + + + + + + + + + + + org.alfresco.service.cmr.search.CategoryService + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${dir.indexes} + + + + + + + + + ${lucene.maxAtomicTransformationTime} + + + ${lucene.query.maxClauses} + + + ${lucene.indexer.batchSize} + + + ${lucene.indexer.minMergeDocs} + + + ${lucene.indexer.mergeFactor} + + + ${lucene.indexer.maxMergeDocs} + + + ${dir.indexes.lock} + + + ${lucene.indexer.maxFieldLength} + + + ${lucene.write.lock.timeout} + + + ${lucene.commit.lock.timeout} + + + ${lucene.lock.poll.interval} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.alfresco.repo.version.common.counter.VersionCounterService + + + + + + + + + + ${server.transaction.mode.default}, PROPAGATION_REQUIRES_NEW + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + alfresco/model/dictionaryModel.xml + alfresco/model/systemModel.xml + alfresco/model/contentModel.xml + alfresco/model/bpmModel.xml + alfresco/model/wcmModel.xml + alfresco/model/applicationModel.xml + alfresco/model/forumModel.xml + + + org/alfresco/repo/security/authentication/userModel.xml + org/alfresco/repo/action/actionModel.xml + org/alfresco/repo/rule/ruleModel.xml + org/alfresco/repo/version/version_model.xml + + + + + alfresco/model/dataTypeAnalyzers + alfresco/messages/system-model + alfresco/messages/dictionary-model + alfresco/messages/content-model + alfresco/messages/bpm-messages + alfresco/messages/application-model + alfresco/messages/forum-model + + + + + + + + alfresco/model/defaultCustomModel.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + + + + + + + + + + + ${dir.root}/backup-lucene-indexes + + + + + + + + + + 5 + + + 20 + + + 60 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.alfresco.repo.configuration.ConfigurableService + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + diff --git a/config/alfresco/dbscripts/create/1.4/org.hibernate.dialect.Dialect/post-create-indexes.sql b/config/alfresco/dbscripts/create/1.4/org.hibernate.dialect.Dialect/post-create-indexes.sql new file mode 100644 index 0000000000..d2bc282c82 --- /dev/null +++ b/config/alfresco/dbscripts/create/1.4/org.hibernate.dialect.Dialect/post-create-indexes.sql @@ -0,0 +1,3 @@ +-- +-- Add post-creation indexes. (Generic Schema 1.4) +-- diff --git a/config/alfresco/dbscripts/create/1.4/org.hibernate.dialect.Oracle9Dialect/post-create-indexes.sql b/config/alfresco/dbscripts/create/1.4/org.hibernate.dialect.Oracle9Dialect/post-create-indexes.sql new file mode 100644 index 0000000000..373194f988 --- /dev/null +++ b/config/alfresco/dbscripts/create/1.4/org.hibernate.dialect.Oracle9Dialect/post-create-indexes.sql @@ -0,0 +1,30 @@ +-- +-- Add post-creation indexes. (Oracle Schema 1.4) +-- +CREATE INDEX FKFFF41F9960601995 ON alf_access_control_entry (permission_id); +CREATE INDEX FKFFF41F99B25A50BF ON alf_access_control_entry (authority_id); +CREATE INDEX FKFFF41F99B9553F6C ON alf_access_control_entry (acl_id); +CREATE INDEX FK8A749A657B7FDE43 ON alf_auth_ext_keys (id); +CREATE INDEX FKFFC5468E74173FF4 ON alf_child_assoc (child_node_id); +CREATE INDEX FKFFC5468E8E50E582 ON alf_child_assoc (parent_node_id); +CREATE INDEX FK60EFB626B9553F6C ON alf_node (acl_id); +CREATE INDEX FK60EFB626D24ADD25 ON alf_node (protocol, identifier); +CREATE INDEX FK7D4CF8EC7F2C8017 ON alf_node_properties (node_id); +CREATE INDEX FKD654E027F2C8017 ON alf_node_aspects (node_id); +CREATE INDEX FKE1A550BCB69C43F3 ON alf_node_assoc (source_node_id); +CREATE INDEX FKE1A550BCA8FC7769 ON alf_node_assoc (target_node_id); +CREATE INDEX FK71C2002B7F2C8017 ON alf_node_status (node_id); +CREATE INDEX FKBD4FF53D22DBA5BA ON alf_store (root_node_id); + +-- +-- Transaction tables +-- +CREATE INDEX FK71C2002B9E57C13D ON alf_node_status (transaction_id); +CREATE INDEX FKB8761A3A9AE340B7 ON alf_transaction (server_id); + +-- +-- New audit tables +-- +CREATE INDEX FKEAD1817484342E39 ON alf_audit_fact (audit_date_id); +CREATE INDEX FKEAD18174A0F9B8D9 ON alf_audit_fact (audit_source_id); +CREATE INDEX FKEAD18174F524CFD7 ON alf_audit_fact (audit_conf_id); diff --git a/config/alfresco/dbscripts/create/1.4/org.hibernate.dialect.SQLServerDialect/post-create-indexes.sql b/config/alfresco/dbscripts/create/1.4/org.hibernate.dialect.SQLServerDialect/post-create-indexes.sql new file mode 100644 index 0000000000..1a385c5260 --- /dev/null +++ b/config/alfresco/dbscripts/create/1.4/org.hibernate.dialect.SQLServerDialect/post-create-indexes.sql @@ -0,0 +1,30 @@ +-- +-- Add post-creation indexes. (SQL Server Schema 1.4) +-- +CREATE INDEX FKFFF41F9960601995 ON alf_access_control_entry (permission_id); +CREATE INDEX FKFFF41F99B25A50BF ON alf_access_control_entry (authority_id); +CREATE INDEX FKFFF41F99B9553F6C ON alf_access_control_entry (acl_id); +CREATE INDEX FK8A749A657B7FDE43 ON alf_auth_ext_keys (id); +CREATE INDEX FKFFC5468E74173FF4 ON alf_child_assoc (child_node_id); +CREATE INDEX FKFFC5468E8E50E582 ON alf_child_assoc (parent_node_id); +CREATE INDEX FK60EFB626B9553F6C ON alf_node (acl_id); +CREATE INDEX FK60EFB626D24ADD25 ON alf_node (protocol, identifier); +CREATE INDEX FK7D4CF8EC7F2C8017 ON alf_node_properties (node_id); +CREATE INDEX FKD654E027F2C8017 ON alf_node_aspects (node_id); +CREATE INDEX FKE1A550BCB69C43F3 ON alf_node_assoc (source_node_id); +CREATE INDEX FKE1A550BCA8FC7769 ON alf_node_assoc (target_node_id); +CREATE INDEX FK71C2002B7F2C8017 ON alf_node_status (node_id); +CREATE INDEX FKBD4FF53D22DBA5BA ON alf_store (root_node_id); + +-- +-- Transaction tables +-- +CREATE INDEX FK71C2002B9E57C13D ON alf_node_status (transaction_id); +CREATE INDEX FKB8761A3A9AE340B7 ON alf_transaction (server_id); + +-- +-- New audit tables +-- +CREATE INDEX FKEAD1817484342E39 ON alf_audit_fact (audit_date_id); +CREATE INDEX FKEAD18174A0F9B8D9 ON alf_audit_fact (audit_source_id); +CREATE INDEX FKEAD18174F524CFD7 ON alf_audit_fact (audit_conf_id); diff --git a/config/alfresco/dbscripts/upgrade/1.4/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoSchemaUpdate-1.4-1.sql b/config/alfresco/dbscripts/upgrade/1.4/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoSchemaUpdate-1.4-1.sql index 9b14fec91c..350604fc57 100644 --- a/config/alfresco/dbscripts/upgrade/1.4/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoSchemaUpdate-1.4-1.sql +++ b/config/alfresco/dbscripts/upgrade/1.4/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoSchemaUpdate-1.4-1.sql @@ -1,5 +1,5 @@ -- ------------------------------------------------------ --- Alfresco Schema conversion V1.3 to V1.4 Part 1 +-- Alfresco Schema conversion V1.3 to V1.4 Part 1 (MySQL) -- -- Adds the columns required to enforce the duplicate name detection -- @@ -25,6 +25,11 @@ DROP TABLE IF EXISTS T_permission; DROP TABLE IF EXISTS T_store; DROP TABLE IF EXISTS T_version_count; +-- +-- Upgrades to 1.3 of MyIsam tables could have missed the applied_patch table InnoDB +-- +ALTER TABLE applied_patch ENGINE = InnoDB; + -- -- Unique name constraint -- @@ -47,20 +52,70 @@ ALTER TABLE node_assoc -- -- Rename tables to give 'alf_' prefix -- -ALTER TABLE access_control_entry RENAME TO alf_access_control_entry; -ALTER TABLE access_control_list RENAME TO alf_access_control_list; -ALTER TABLE applied_patch RENAME TO alf_applied_patch; -ALTER TABLE auth_ext_keys RENAME TO alf_auth_ext_keys; -ALTER TABLE authority RENAME TO alf_authority; -ALTER TABLE child_assoc RENAME TO alf_child_assoc; -ALTER TABLE node RENAME TO alf_node; -ALTER TABLE node_aspects RENAME TO alf_node_aspects; -ALTER TABLE node_assoc RENAME TO alf_node_assoc; -ALTER TABLE node_properties RENAME TO alf_node_properties; -ALTER TABLE node_status RENAME TO alf_node_status; -ALTER TABLE permission RENAME TO alf_permission; -ALTER TABLE store RENAME TO alf_store; -ALTER TABLE version_count RENAME TO alf_version_count; +ALTER TABLE access_control_entry RENAME TO alf_access_control_entry; +ALTER TABLE access_control_list RENAME TO alf_access_control_list; +ALTER TABLE applied_patch RENAME TO alf_applied_patch; +ALTER TABLE auth_ext_keys RENAME TO alf_auth_ext_keys; +ALTER TABLE authority RENAME TO alf_authority; +ALTER TABLE child_assoc RENAME TO alf_child_assoc; +ALTER TABLE node RENAME TO alf_node; +ALTER TABLE node_aspects RENAME TO alf_node_aspects; +ALTER TABLE node_assoc RENAME TO alf_node_assoc; +ALTER TABLE node_properties RENAME TO alf_node_properties; +ALTER TABLE node_status RENAME TO alf_node_status; +ALTER TABLE permission RENAME TO alf_permission; +ALTER TABLE store RENAME TO alf_store; +ALTER TABLE version_count RENAME TO alf_version_count; + +-- +-- The table renames will cause Hibernate to rehash the FK constraint names. +-- For MySQL, Hibernate will generate scripts to add the appropriate constraints +-- and indexes. +-- +ALTER TABLE alf_access_control_entry + DROP FOREIGN KEY FKF064DF7560601995, + DROP INDEX FKF064DF7560601995, + DROP FOREIGN KEY FKF064DF75B25A50BF, + DROP INDEX FKF064DF75B25A50BF, + DROP FOREIGN KEY FKF064DF75B9553F6C, + DROP INDEX FKF064DF75B9553F6C; +ALTER TABLE alf_auth_ext_keys + DROP FOREIGN KEY FK31D3BA097B7FDE43, + DROP INDEX FK31D3BA097B7FDE43; +ALTER TABLE alf_child_assoc + DROP FOREIGN KEY FKC6EFFF3274173FF4, + DROP INDEX FKC6EFFF3274173FF4, + DROP FOREIGN KEY FKC6EFFF328E50E582, + DROP INDEX FKC6EFFF328E50E582;(optional) +ALTER TABLE alf_child_assoc + DROP FOREIGN KEY FKFFC5468E74173FF4, + DROP INDEX FKFFC5468E74173FF4, + DROP FOREIGN KEY FKFFC5468E8E50E582, + DROP INDEX FKFFC5468E8E50E582;(optional) +ALTER TABLE alf_node + DROP FOREIGN KEY FK33AE02B9553F6C, + DROP INDEX FK33AE02B9553F6C; +ALTER TABLE alf_node + DROP FOREIGN KEY FK33AE02D24ADD25, + DROP INDEX FK33AE02D24ADD25; +ALTER TABLE alf_node_properties + DROP FOREIGN KEY FKC962BF907F2C8017, + DROP INDEX FKC962BF907F2C8017; +ALTER TABLE alf_node_aspects + DROP FOREIGN KEY FK2B91A9DE7F2C8017, + DROP INDEX FK2B91A9DE7F2C8017; +ALTER TABLE alf_node_assoc + DROP FOREIGN KEY FK5BAEF398B69C43F3, + DROP INDEX FK5BAEF398B69C43F3; +ALTER TABLE alf_node_assoc + DROP FOREIGN KEY FK5BAEF398A8FC7769, + DROP INDEX FK5BAEF398A8FC7769; +ALTER TABLE alf_node_status + DROP FOREIGN KEY FK38ECB8CF7F2C8017, + DROP INDEX FK38ECB8CF7F2C8017; +ALTER TABLE alf_store + DROP FOREIGN KEY FK68AF8E122DBA5BA, + DROP INDEX FK68AF8E122DBA5BA; -- -- Record script finish diff --git a/config/alfresco/dbscripts/upgrade/1.4/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoSchemaUpdate-1.4-2.sql b/config/alfresco/dbscripts/upgrade/1.4/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoSchemaUpdate-1.4-2.sql index 9a3c77833f..60ad676333 100644 --- a/config/alfresco/dbscripts/upgrade/1.4/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoSchemaUpdate-1.4-2.sql +++ b/config/alfresco/dbscripts/upgrade/1.4/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoSchemaUpdate-1.4-2.sql @@ -1,5 +1,5 @@ -- ------------------------------------------------------ --- Alfresco Schema conversion V1.3 to V1.4 Part 2 +-- Alfresco Schema conversion V1.3 to V1.4 Part 2 (MySQL) -- -- Adds the alf_transaction and alf_server tables to keep track of the sources -- of transactions. @@ -25,7 +25,6 @@ CREATE TABLE alf_transaction ( change_txn_id varchar(56) NOT NULL, PRIMARY KEY (id), KEY FKB8761A3A9AE340B7 (server_id), - KEY IDX_CHANGE_TXN (change_txn_id), CONSTRAINT FKB8761A3A9AE340B7 FOREIGN KEY (server_id) REFERENCES alf_server (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into alf_transaction @@ -44,7 +43,11 @@ UPDATE alf_node_status ns SET ns.transaction_id = ); ALTER TABLE alf_node_status DROP COLUMN change_txn_id, - ADD CONSTRAINT FK71C2002B9E57C13D FOREIGN KEY (transaction_id) REFERENCES alf_transaction (id); + ADD CONSTRAINT FK71C2002B9E57C13D FOREIGN KEY (transaction_id) REFERENCES alf_transaction (id), + ADD INDEX FK71C2002B9E57C13D (transaction_id); +ALTER TABLE alf_node_status + DROP COLUMN deleted + ;(optional) -- -- Record script finish diff --git a/config/alfresco/dbscripts/upgrade/1.4/org.hibernate.dialect.Oracle9Dialect/AlfrescoSchemaUpdate-1.4-1.sql b/config/alfresco/dbscripts/upgrade/1.4/org.hibernate.dialect.Oracle9Dialect/AlfrescoSchemaUpdate-1.4-1.sql new file mode 100644 index 0000000000..f2fdd876b9 --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/1.4/org.hibernate.dialect.Oracle9Dialect/AlfrescoSchemaUpdate-1.4-1.sql @@ -0,0 +1,92 @@ +-- ------------------------------------------------------ +-- Alfresco Schema conversion V1.3 to V1.4 Part 1 (Oracle) +-- +-- Adds the columns required to enforce the duplicate name detection +-- +-- Author: Derek Hulley +-- ------------------------------------------------------ + +-- +-- Unique name constraint +-- + +-- Apply new schema changes to child assoc table +ALTER TABLE child_assoc ADD + ( + child_node_name VARCHAR2(50 CHAR) DEFAULT 'V1.4 upgrade' NOT NULL, + child_node_name_crc NUMBER(19,0) DEFAULT -1 NOT NULL + ); + +UPDATE child_assoc + SET child_node_name_crc = id * -1; + +CREATE UNIQUE INDEX IDX_CHILD_NAMECRC ON child_assoc (parent_node_id, type_qname, child_node_name, child_node_name_crc); + +-- Apply unique index for node associations +CREATE UNIQUE INDEX IDX_ASSOC ON node_assoc (source_node_id, type_qname, target_node_id); + +-- +-- Rename tables to give 'alf_' prefix +-- +ALTER TABLE access_control_entry RENAME TO alf_access_control_entry; +ALTER TABLE access_control_list RENAME TO alf_access_control_list; +ALTER TABLE applied_patch RENAME TO alf_applied_patch; +ALTER TABLE auth_ext_keys RENAME TO alf_auth_ext_keys; +ALTER TABLE authority RENAME TO alf_authority; +ALTER TABLE child_assoc RENAME TO alf_child_assoc; +ALTER TABLE node RENAME TO alf_node; +ALTER TABLE node_aspects RENAME TO alf_node_aspects; +ALTER TABLE node_assoc RENAME TO alf_node_assoc; +ALTER TABLE node_properties RENAME TO alf_node_properties; +ALTER TABLE node_status RENAME TO alf_node_status; +ALTER TABLE permission RENAME TO alf_permission; +ALTER TABLE store RENAME TO alf_store; +ALTER TABLE version_count RENAME TO alf_version_count; + +-- +-- The table renames will cause Hibernate to rehash the FK constraint names +-- +ALTER TABLE alf_access_control_entry RENAME CONSTRAINT FKF064DF7560601995 TO FKFFF41F9960601995; +ALTER TABLE alf_access_control_entry RENAME CONSTRAINT FKF064DF75B25A50BF TO FKFFF41F99B25A50BF; +ALTER TABLE alf_access_control_entry RENAME CONSTRAINT FKF064DF75B9553F6C TO FKFFF41F99B9553F6C; +ALTER TABLE alf_auth_ext_keys RENAME CONSTRAINT FK31D3BA097B7FDE43 TO FK8A749A657B7FDE43; +ALTER TABLE alf_child_assoc RENAME CONSTRAINT FKC6EFFF3274173FF4 TO FKFFC5468E74173FF4; +ALTER TABLE alf_child_assoc RENAME CONSTRAINT FKC6EFFF328E50E582 TO FKFFC5468E8E50E582; +ALTER TABLE alf_node RENAME CONSTRAINT FK33AE02B9553F6C TO FK60EFB626B9553F6C; +ALTER TABLE alf_node RENAME CONSTRAINT FK33AE02D24ADD25 TO FK60EFB626D24ADD25; +ALTER TABLE alf_node_properties RENAME CONSTRAINT FKC962BF907F2C8017 TO FK7D4CF8EC7F2C8017; +ALTER TABLE alf_node_aspects RENAME CONSTRAINT FK2B91A9DE7F2C8017 TO FKD654E027F2C8017; +ALTER TABLE alf_node_assoc RENAME CONSTRAINT FK5BAEF398B69C43F3 TO FKE1A550BCB69C43F3; +ALTER TABLE alf_node_assoc RENAME CONSTRAINT FK5BAEF398A8FC7769 TO FKE1A550BCA8FC7769; +ALTER TABLE alf_node_status RENAME CONSTRAINT FK38ECB8CF7F2C8017 TO FK71C2002B7F2C8017; +ALTER TABLE alf_store RENAME CONSTRAINT FK68AF8E122DBA5BA TO FKBD4FF53D22DBA5BA; + +-- +-- Rename the indexes to keep in synch with the new table names. For Oracle, Hibernate doesn't create or add these +-- +ALTER INDEX FKF064DF7560601995 RENAME TO FKFFF41F9960601995; +ALTER INDEX FKF064DF75B25A50BF RENAME TO FKFFF41F99B25A50BF; +ALTER INDEX FKF064DF75B9553F6C RENAME TO FKFFF41F99B9553F6C; +ALTER INDEX FK31D3BA097B7FDE43 RENAME TO FK8A749A657B7FDE43; +ALTER INDEX FKC6EFFF3274173FF4 RENAME TO FKFFC5468E74173FF4; +ALTER INDEX FKC6EFFF328E50E582 RENAME TO FKFFC5468E8E50E582; +ALTER INDEX FK33AE02B9553F6C RENAME TO FK60EFB626B9553F6C; +ALTER INDEX FK33AE02D24ADD25 RENAME TO FK60EFB626D24ADD25; +ALTER INDEX FKC962BF907F2C8017 RENAME TO FK7D4CF8EC7F2C8017; +ALTER INDEX FK2B91A9DE7F2C8017 RENAME TO FKD654E027F2C8017; +ALTER INDEX FK5BAEF398B69C43F3 RENAME TO FKE1A550BCB69C43F3; +ALTER INDEX FK5BAEF398A8FC7769 RENAME TO FKE1A550BCA8FC7769; +ALTER INDEX FK38ECB8CF7F2C8017 RENAME TO FK71C2002B7F2C8017; +ALTER INDEX FK68AF8E122DBA5BA RENAME TO FKBD4FF53D22DBA5BA; + +-- +-- Record script finish +-- +delete from alf_applied_patch where id = 'patch.schemaUpdateScript-V1.4-1'; +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.schemaUpdateScript-V1.4-1', 'Manually execute script upgrade V1.4 part 1', + 0, 19, -1, 20, sysdate, 'UNKOWN', 1, 1, 'Script completed' + ); diff --git a/config/alfresco/dbscripts/upgrade/1.4/org.hibernate.dialect.Oracle9Dialect/AlfrescoSchemaUpdate-1.4-2.sql b/config/alfresco/dbscripts/upgrade/1.4/org.hibernate.dialect.Oracle9Dialect/AlfrescoSchemaUpdate-1.4-2.sql new file mode 100644 index 0000000000..ee3de98dad --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/1.4/org.hibernate.dialect.Oracle9Dialect/AlfrescoSchemaUpdate-1.4-2.sql @@ -0,0 +1,69 @@ +-- ------------------------------------------------------ +-- Alfresco Schema conversion V1.3 to V1.4 Part 2 (Oracle) +-- +-- Adds the alf_transaction and alf_server tables to keep track of the sources +-- of transactions. +-- +-- Author: Derek Hulley +-- ------------------------------------------------------ + +-- +-- Create server and transaction tables +-- + +create table alf_server +( + id number(19,0) not null, + ip_address varchar2(15 char) not null, + primary key (id), + unique (ip_address) +); +insert into alf_server (id, ip_address) values (0, '0.0.0.0'); + +create table alf_transaction +( + id number(19,0) not null, + server_id number(19,0), + change_txn_id varchar2(56 char) not null, + primary key (id) +); +alter table alf_transaction add constraint FKB8761A3A9AE340B7 foreign key (server_id) references alf_server; +create index FKB8761A3A9AE340B7 on alf_transaction (server_id); + +insert into alf_transaction + ( + id, server_id, change_txn_id + ) + select + hibernate_sequence.nextval, + (select max(id) from alf_server), + change_txn_id + from alf_node_status; + +-- Alter node status +alter table alf_node_status add + ( + transaction_id number(19,0) DEFAULT 0 NOT NULL + ); +-- Update FK column +update alf_node_status ns SET ns.transaction_id = + ( + select t.id from alf_transaction t + where t.change_txn_id = ns.change_txn_id and rownum = 1 + ); +alter table alf_node_status DROP COLUMN change_txn_id; +alter table alf_node_status ADD CONSTRAINT FK71C2002B9E57C13D FOREIGN KEY (transaction_id) REFERENCES alf_transaction (id); +create index FK71C2002B9E57C13D on alf_node_status (transaction_id); +alter table alf_node_status DROP COLUMN deleted;(optional) + +-- +-- Record script finish +-- +delete from alf_applied_patch where id = 'patch.schemaUpdateScript-V1.4-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.schemaUpdateScript-V1.4-2', 'Manually execute script upgrade V1.4 part 2', + 0, 20, -1, 21, sysdate, 'UNKOWN', 1, 1, 'Script completed' + ); diff --git a/config/alfresco/desktop/Alfresco.exe b/config/alfresco/desktop/Alfresco.exe index 2bc8239ab159b0435d0c3b24b280cc28c48d50fe..1b2dfaf2058dbbbff957b5bd628e8ee0bc4a1e8a 100644 GIT binary patch literal 327680 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
  • ^4LA(rDI2*U~R9+YzJ-G6XgK5zv! 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
    g*#}t%xo=%8^JU`UoZ#`Y$}tDm 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 tPo?+?*m4bk_7=<7ms__=Y`!(Pvwe=I~V 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 literal 53248 zcmeIb3tUv!wLiXx8FAE6#%5@+CCS8&y(CzJLJ|xZ2L*x#ba)6FiHI;b5Cj4Fo0ZB3ioYaTb!Hf=$OU_;a-HZjq(wDqQYf=NkZ4M|MS|GV}%XC8<# zZPU-^|M~nly3c;Cz4lsbuf6v340(6$WO~M!0Zx_~>wr&>UA#Z}#gRSbx?fCTPfd9K z`VL+0^Vb)-s++70jrD6A%hy>e%IoUt-PScuYon*mT3u(&Tv}*dS6}IzHF@$xn~HkX z*S~Ye7v31QXwE>yFc>j)unjtOrBA+&CV#NSbC>7S|r6%(uGW$QRkje%%8|(%-ds-j;uOt z5P&M08rfCtva`y4M5iOac;Cs#?JT*+;m@?0D)&>O^p4MMGs>}#ZM8Eg(-!aWFERCI z+Ttj$H`-3M$L^uLBT_EP_$E`vidEhoGu7nXXfrn&qzrSnA5PFm<<)Y#soXI+lGO3x zq=KJI;8%s%?U!fl0*;VOz|YS=4L|;Gv=jZAHj5s<97|aIOI9C2LX;M>Q%tADY!p)= zrDIf5rbm<<$$6-iq>NOluPZSALYK}n{=j^Sk+ENiKERmkEIS_D7#7;%>Y3RbtLI?kfLrwATuw^2nFrR+` zNm8qAHDJ9xao*-QW8*j}$1te(=NSA&wggFzIi#!dMR~iTyoaNz(z-o=1$ZujN5by1 zr2wOU5xWFFxzh(_gEPXJ;LLCqxOg~g6<#)fuFXib`CA2+d|#=JG}KJnEWRQE99Mr{>XTpw8LfxVPz|m`}v%ujBV{9$s9I?DHqht z+hvGQIkeaM>R|ubdhR4D4N3C^iD_AJNw8P7+BlNbj@4=#Mnrndb`jn(aM28I;&}BO z#%fs23?yvoRbapfObj1o0{17OThP^UsPC~Jg zV*f7NZWJlda7P2TYq>(tE@ZUt5%~Em6m0EuUj@5i(dy;_)HzrRJs`+mV>^${eadzK z9@;FPfwQLZXi3U+xG0KB&LNUdOg{(`ExP06j~fDrbBpuzP!TX`3xg z<`R|u_aGf^P647_;bMp`82k^2+Owbp74(Y=&>Van+5m;a z~O2 z=~q}7$vu@StY^zHs`eWt8qsE1_Q?4=)oxINU+tZ-B)iz7aF#1z8h58)RqA_`kxrp8>6IKilI6Q zE;qWgL*+ZymZY{vKHrip_(oac8*JpF@*oc@0SAf?>PAftjnxzaKY2a*CrFeNk|weXsO5%7Mf=$c1Z7VSTngdI@b&J4WAQ%o@q5f zd+t4#88qFQwj4#it^;J~Fy_dyJ89McjHD$th_49B#`u8?vh2$JmH@KlBRPSp;qO9; z`ymp8_nfKi=!VZHAN5=ORG@&T;Isp?Gsvym+b1W#_})9-VMEgZP@4(00XQrKDaUr8 zMpxW@0=I#18)>q~ze~933E__?{LfRV#2550>R&A2$1*RiCBEskPhZPytrk(0QlBbM zrDc?iEb;g#Wcp3d3Q6|qkLr&~uP45m_OaWN__ic_-;OW#n`ZHovlvTz z&ohJ`EOoEcu+w}OlV`$gPJ7QCTPT&<;tD~`4*~8y5|0w^tSk!>L+uB7q798{UGAw; zk%k^ta!A(dRLHd*cURkv!+5&BSC3P3UPlt@4+Ay0DPH2L;ChA@6*cm5~1L z?(Y74vJqg&e8ga5lELNxgBD7(6d={&$S>e4FiLuR;uV=H{A~Sn{1~Oprjh)7xPFn( zV+ye|Ny;)iz|p%2;OMV95G|N84*pJXWV{?lGl-*0`4R1d9!qVP*ah|V8$|OI`%}#B z$&P#n#>`OjjGSGH^^uf~F5{ly;2+NiNj-+%Yy-7KZ?;kKOyp@glTWe}dLWeS2-Jhd zVqK6*Q3&rBFw*lAPo%xnG>)(NK$b`5>$}s2m&JIW0$)<^ z3tR(#rHN{Y(hrXY-}E_=Dkv}t3iKb=uo8hE^m%)X5{K~OmO1jF7BgvzMl+~FQj%kc zK{w+ZAW&parUYs)(0vqiFM*ZodOu44s?vh#w~H3^7ySyu#1ELS&Tw~`uh`_vHm+KE z*vzClBjg?@J)d|Sh5UvePy?kshxuE)bgU^`-|JXb!hiA+RbiC+6dMohk>c z4!P@T1%#X-G;=?&$7Z2)Xfh80Bj_{^3QbWkLQ>0Hc=!_ zEuxrW@h+5JEtZ-Lu%WgR>Ayzn4bnn`gBQ*djFzqHU8pGQLPc2@3S|wmhxvueU~4VV zh!Sawp-pz1LcBqSrw6Z38(tqR$9BSiw7umvq`mD{W6%?8;vc#`jDmvNz_zuZM(0?D zMtOh)w8fCN#i$kZVXoj$e+VfX#-(jBG-()UA=zvw^Ce-LwF2@7hob+4+a5AIW->r= zWP5%ErGxDW^;7RTBWZInjZ(oT|1Dat?M1gu$~FnRoYvPAM{JR4o9Z*ed{c`>X*_@6n=VDxlH`ioH7!Ztqf+Dao_GtaGsBktRILBE94A=bi9* zp5fQSfJ>{;pVChGvM##O`Amt=jWV{uY`4k2tp1qR?_;Xjc3N~nKpRJe{`sQ&Y!edu z(vG7`9>+xIO7K!%zrHuEFUFgJ{w;N(lL`bU(q3qq(3{my8b3Hr-{;HgzY->W@7%2Z zCKJT;i?BbomOtqVRQ3*{G5Ru7E$x2ON5pmFaaaOGeZ@v!wjppGi6^U1@^ngh&p3+tw}Gdae>hhlp%oi{62=77 z5~D99zT{ihqqOCl`ei-7yw1>+Dy`RV5H-Yj@1uHp^__Z>g1+04PZUm|{z9$mUv{Ek z6n#h>1}`Kch)vJG|309z`cxYO;lacd_GVoq7WlOgJS@$iQ_3^@4a#J*3-n9*MygbQ z0u5o(A9nC(fkiO!HVg$(l&|lU-p@}Q#$J%y0W|vxR14xjbvYjIDG22Oam||qwGO@XofQC=&#%d)eruC_DqP0d$ zdVkv$xK97unAY!5?lBZe=dYbdC+dv(nwOI1k=;Ww)QKhm@zhSEO`FqBHAcy=07ws; z2J2Dl;B%}{mCd;fi(t{KKv>MzzJ|AdSt_mn8~DKWVmHR)&%!3|4zcCJFJ6burYK|= z<>TeXqL?_+@z&|ep2`8lkaz-K6u)T(u;tnuNT4dTtHNxc8CNOO5MT0 z(!Ui@wDfNfOa2BdEX^%;EJU%bRBT|ZiyLgZ_DtK=nEAHa&H(s=J@rP&3VG5FB_Sg< zHm3D4B-E5*ylW`H2$tgDKmG#s5)-B}Nn#U>h|{~Th5b!QJT{7kdxv!!q9H!O`?G~C zcAx|wx)x}jv9(ZXm3TvAa*e)*R`1J6KFvHHa`9s5)VVs^?M&)ZWiyU1gdnliWq49I z7&YfK_T3Y7YD<7x=8o}J+7dn{;VHV?iW$$h(B-$6+OBRr?O81C5)4WUQoDH60iCCi zHa=VpsDn?vRfx15XjH27>cZKftE~*r;c$0sLb}AS*yNx9ZT34H()0Xy5+rTJ2?7$arR2sqn`_H$JE~R^Exqra!oSC58M7tq z_+f5}dBIKIEu}1`EeRC_nMfB*xNmIPVr3p1SV?l7`Z{)lXtO;?T6));Q5O#DV%nx5 z6(n5kpgmAGjlH>;$9{?NvVbhNcE+?;0-(mqr_k13j{%*{+SaMkG|`Q9E`)i1mw%Uy zL{xyi=6tD-e;Ye>gHg$yz?LbdMkqj99F5{Oelw(n%4{kY13&o=#J2|xLNqiaU*Hu` z2CAD@=u(!&+o^N-TScFt6}g*NpuLM-C%~0IxM4?KXA*zl8jv|Gd**`Vm5P`mNH)2@ znEYaDk?m>}d;}$8?mrBI3-}+gSt=+VQZqE-l-6#$5YrYQVzj=Iu5;fDQm!L`WiA7c z?gEG{k?{ny5RuLlvAvRVsn}xN2|*f2Y6=b5qc7lhfefu?Y?^LN)>epADN+tA zA*ULvDqJ(K8KttRmtlAa+p@&4%C0n|RjZnJYB{vEgA|c_F$Mte3cVl?i+ptJ9L11g z%9sub)v6saG;(-hHRHYAI@JW1{8g6IPQ`5dI>ub=TJ5m)KqR4+s@Riz3`y9Ni$iDJ z+Vcz|7-B>p-3b*?_Jv2QX-?Z9tD~Ic2CP)fI z0|u$;zU+wZs|ss;SU0Av${e7J*s7#0)T9Fb9GX?ZCAKQV>pJx|>iQnW$xvM_n`~Dz zPgUz_wHD*gEx_v;_w%#Z`x?cR=Zl64?i6B&RP5a5^P!-ZL7kzkW(~K44?}-0!_qjy-HYaoVCg#MSuQwJYngmSu5xYGON41I;9DYU4y-~& zqcAZ=V*+b5!9-{)RbwKbCr6^X3aG-SE3`j4u{Jng@t#|*O;)hk7%@}HwAHHHzX5cB zO|TLzP*NP^wZcn2-Hvev^9n1R4K4w4iN`#+SbE;KM}Y7@p@Wgtcm+mKx+7IO(e>td zm}gHqPAyYuy_MGOo&-~46DwTLj6xXo9v^h~UK%x&d~8}Qx=C0Y?7Z&>B5J>a5-118 zmt%Zsf`H6P>xQ9n>tb4`0OU7D5SZ5M`8-I<%?CmJz*$+Qc|hQHWP69lBdf@kTR>SK z!L*62FA;K#fBdvX!RhV%7<;CKH)_L|WLuj%2^*!4fWE;mxZ26P%2h<3$(2IhIF}I~ zFeLH!F?&EjJN+oN(J8Q7zdm{HCR-&Kc8C)K2V$9EGf6aR5)KByvTGOvwA!lJa2uQ! z2_y~=g)9`4H@FlBlB?G*3X$Ul4K!zR5R^gG?Vz)g zcvmN?j_ehMHFgL8Gj0474V0kBrql^TLE#j6|i4y1*m|UwEO#&{eWMGOv2W44d@D=uqE)m z9Lq=nkMn_<7^)e63EsYwTajdxreZ!-;Lm%CTdqQLKPML|Gn%}ov=!bt>Rv-_7dBrd zhOYMQ=pN^Cl0=QNI zuyHZ1`_TIS`|YBlyc2u|v9tLsLuRyRva1$kWA(_VfW)~TE@ho0#3#Yh^$z}cB4CH3 z2oGR9MM_`s`>9T%fxCW>aYQ3GiciC-6L>_HwgB=IgNUn39Waj#bOlXvXCNPJsr|1l z6@YXfBpQUf@A2oI;orxR+mqw5=lp9lBikv(o}g3TZFZ~LghIAE7E(AB`J{n ztur*pxMx$f`_LGdAOLTZ2#FyKNi&dyHOH&K0riCbg_J0Yd&!Dlp;b@{@>h2c&@zVv zioKv)Jp}z#GlE8`tr8w6nbH*P7h#Bmpj_$DBW*r#Kc%SioC02p0}%yCJ)`!oR&bd3 ziqo>(G390)TZt|-jB$t06W}BnP+1OP#`?4)%N3xAN?ewp|zh$FQ=b5Zg z{o;QpREIdi%`|M@>!l1+VupqI5pi92Vunc^I0e4{YQ%&LMqN2St}sH_;U~umPVc#R z+8(--s?J zMLYQ4px_uLyxArLc7)aAul`Z+8%N@QGy?zPNc`(G{D*`1X=_@;kNM`bbz$>O+lWbK zsyfNsWXnlD4MvY+sI_CiJr2HwFPX$xYAFu!YHhicLFd+F*4%3#3uy^z@tDKdRU3MTCGFX!3H~A$r7JcK z-sR8y3e#nq2JJOCPhf{0QrcqBqsx`>ni)`< zDbguy++i=4m!T~%ZEV?MWSClizJSj`zM{`%zH3R5ae@M#Gh_2;lHn0q%QN<>Y|qMpMNQ$ z!G0!eS6G8>MLr3s&Y-l{-NefCZI%FLz)9L2mi1SWrhfj2VItG5 zG9`8+X~SDYm^e6vcJZ&+yTWm2;7a4W!MK%;GGDaZ~MFBkmdI}g2Fi>C|0^=wUy*<-79;>VI zlEH?wXwZS?H7W=1s>y_XSQBm^5<=_Ik)|SDH5Hk*wjlt~ZSOp(^Y@ zPNLH@s~o!n@ZR1iN;0H4G?{)H#+t%ZGqubS;ML}2bZt33yq64++%!zOdw+tx~Wilt?R?M_EMvs0%ioFC}2cD*KRS2Q%}ficeuQaBkXCsA|!N{ zbR8&8@!SP%o+&M)8!1W2I2c$zCv+g{{V#FBb|GD`U1*3oWN2#qaSMXUs3isN6# z_ULwFyXjgL=VnXe8w%3#M-0~zQh-??O&caTvT|NSa*Pj`Bh(%DJ5Vf+I)DY)bkV~^ z7Rx}*kMSON4{*v_%zuQ*ta4#xMR!a3MCMP?NnLI;=t(=$Gy#3LCEd(C?-G1s7fPT> zUxHC5b2p-WAn(4Ayg%VS8Gjf=fG%t$gCKG5&5c_V$M~BQkq&|2Y}(?=lHLX#SJ{(pj44g!!1b z(?`)J1P{6Agv)P%ilau!%MH)_vYNL<$#aC~O;q#dD0xNUd2fSsqU-aNM}nIfQfP0_ z%){|Q#L!|3?hrz2FtpPSAzpEGZvgi~keFXUYDDXInjgjow@QS}A@6U6%X@XmewL0f z4Q^HVkBR*`967c_+{rR_>^9jp`D!uc!hJQq5~PaJ1|m-tIW!S_7FG@kMVN$b;SzjOrT*50MFBPZ za_pV907}pQ2+NHGnwxD>=4NfOXBKulU}G=^JjRDm1WK?;T#roWk6!{mXvG2qC~Rb- z53A7Q22YShPv=_+zQ~qZ0CTkr`?)|{M5b#Ig~1!4)>h!)ouZn$J&P7`hnfx>__?-H z*z~(_Xyfgv4YL{EbKxTxnlAggwOrS$3q zfm)`+!=8jN)S|rsU_=2EX@(K^w+paaI3V!QY6JIA#9R*iqZ$lSv{TTEY_e=(<_CRQ zQyIRmx9*_bk(q57+14jpz#&w1b0VKh@HXuIHf zL!w*D7L9eWCubfpIYj&Xqw)tOsiZh&kbDZMdpXd}84OHbFlvq6SqSo@j*^sk?ZjP+l08hDcV;QT|FB3zHYt$%(x$45u9~+Ynn=ocJ;hFBI-p zp?OI#t45Iz^z-46{zqEt@Q)RfRMX%Vn-0Q~?yH3CB{L+FSs*?yDWJs%zJ5S5OfvQv z$mpiidVt?gdz(tHHBtghWNNsHOw#PcDl#)L_h#(ad3#bK7u%53KT0o%!+?_myJ=56 z0W#?jAbgFak_*z5F+w5t4dt+wjLfGaw;U5(A~$mJ3wlXk=z9o!7Dv*$WJ62qZ`em@ zG%H;$1gLblP+Z|Z5Na=+4n@o-Zuj!Nj;78ss8XdQG1)uF8jju^G3y9Mijy|oy5<@j3 zL7k@I*GL0W2`;yMQIan^TwxoMERcYYITT&hCQ8I9Lz^q1Gh?u2Rgc^M7F_b+1zH?O}Fhz}yZWf>GP!x&=*SZo!d_wp{-_?{T|<=mL7V%LUW148^u z;&BFDi!45cqFYj>|_vkTpHYfB)4oZJ(E zB3_X=*0EQ!Uy@;oY3;?kCQ6;Uidx2@7EEzeBFFJ(6?+n|QKjIRTSj{yar8|b)c31o zl6&bp**Ln#R*IdgXv{QWjoWGg0lebQnVl~OGUZ$ja^U}o8v>X=O$nvAX!X3=qF+C! zMz2iQpXWV*A^~V}i{_1VU<7s|NkYZxG*`D!9hJ)J%P6E0)A|P36ty7VAZm5rsw@sL zv;mcvwoig7qA8}*JmdLuifI~A&D>RS{w?$|+IW17){CJ>z4_<9{(#wi4RZ0#*?h4_ zuE$$n6NOK`EG@VSnO#&9rO{x=u7r13cIRV5z@a53Dv2I!11*W?d8mDGHj#%*;?Y-k1sN*+4;;-$pbU#s$A5szcFdrq3Et}GQ_}EsGsKL0tq_FRGcqv?I}eh*z**V zg8uQTzTcLzi@*xBLD?T450S)yU;mO+MODBI6!IVc-W>HC?+vpzrCR62E?h*<#5GK{ zTPh1n>wl_re=6&`YrdQ$Ehc_YrbzJZOf(wmwV1 zpw~NWjM+x#8>CZ1xU2#1Rd{Gr`aoehHOT{ifvOEb)gX1+k=rD&fBRjiCyjncRhD+# zEN-MA9v8E#8`5Z2M`UaV&rr@&YM5)Uf@mg>!m`kHcDI5ypSLMH{0>w=hk7Bq*`V1? z(W2;NXwC%y_V&0iJl0H9zj6h&=>`0CTnogKPo42LqrIu;sxw}N28XLwBjKxWX(Z4* zoJRp`aG+5GXrHyA_Q@{{9}cwdHU*LC9fc@W}XVuuvsbo?(OvYH}M z{56V*Uo)V%p8pncG=-HqCCD)FY3L#CtE|3NrR}RW`5tTm555Om@fHh%;0lZ`C0sH* z_S61&iGPbtd~FT`mu+RdkF4}6yOQ)A|2d+l5a(!aEItTC$A74#S(FW&=lK1I2lu$c zWrBiYzuUK!=t9hNBx94#_bsB&v9+rm9$${a+RWwC_AlOzJ0j>rXnwKR%%2o-5}-xL ze}qVHw%EVw%}z%hiYy|w$&`h#a^K>op(gX5YgW3{sNY|_YBww9AWa#VL|+hh2`bgY9=8=k=%pLH-}`aXG4S5=HJp#FxDs$H{{?(4B~1 zJcHxu(jpvBZ@>FEt|D6`Pamw4YLI%B;T8pfobmtu6Jgw~9;EXJ5yjWPk)Oedd-y}J znsgS8_PVVxXpgp@+XA|GqAQ1CMk)zq86pR9t z7{&FtfQe?H@8- z&~|B=ptZ|)nfIazpXc*2SiwNXDI>LVr211(M^l2sz(lBP>7KonpQomUWmL%+$Gw1~ z1c~!z1f^5BwcaYW3`sav-mMgH(KfBZV>G`GpB@OxeRZk6Rp~qi^rMQT0vdf3pz-_z zGRz@{@h#OZnv%APT}k{HjGdn%h#exXk)s0T+FWXi_M<8xl68FbfWYifK^R|(1TpYF z0%jx3v@#sQQt!r&@z$O_$XAwL(67fr`W5V)Jx~>Q_}W7eg{oN3*a!i3RQJLo#gcZ2 z-?KoWI90z$PqD{p!B>mskdCe9duZ?yI#y(J6!7b41DhFyLRop)AGB_c{YcCscL9l* zrDjuxI^+?%lo7@Eo)aoYmm?uyx{JOW>3Z%`nkp9wM20Fle8G>cRqWRB-RO1VOG@#j zR`6pxopIt?VCAjt^c5toyFHjId=Y7^dbL#}2Eec_KjBi3I z93lM{OEU|yp!~G|>#`^c`XDSZrSe!XBn?5usHY(%omT)S?!dNyV7jkZs}iqVS**oH zQ>?u~Nv$F)7wmEzi6)Xq!mXz0c*gHVWu%ED2F8<+Ak>3Kg)sHN%Eb-U*gSz$qZN7D zV1a=K`mwwz%>Iz%L-vR8j<7!m2={)xNI$6i;v=7D;NiBz`xuF5dcO+$qgW2upA^qE zh4g^%O5Os~pxK`y2iJoh&HkkGg|CxtD5fV4x$2hlX89ut8@4=*D z-07F4S!o|>G`cNmgKjhITiMfC%<=!wi}F;9?{@y&8B}>kyc9imd3;P;8)~5p{uGk& znNCa_O+#t-rieI-=NF*^!m?5Nlj7zG&Fx7ze1P<0mzcPA7?Ciy)wUO&N(Nn&S2E{j z+FVf4B5|9xNSadM;IX9G%GU+JMluRouo0pb)O{Y*Ay6v~^Ocz0w#jN8Mt2H`LtD_8 z5zm)`AQEr`vZ+}o@c&lME;O9jv?pT<1#S>a%el&3Y;obZL5j}z=dAWMm+}&&l!ZTd zT1dsS6i6+62r=nR(cX%5K+YL77w!-X{#2qSYJAey#5*vjM&S_zAcZv@#wTaqo*)V6GiGfu#>;%@LI;{r?}S&rkKCE?+d>w#cL5*wZSG*q)?*rofn0Ox)@2AAOL%g36 z?@saV5$`_n?icTq;yobVXT|%Rcn^v9dGTi8&h=oWo$)`xXzgQ9;!26ZHuMCzz4;>E z`zh!epx0S?oxw|v&Dmy$fwQ!A?mvh4W@C+!zmMW8x@$^Z?cWEoWE@pu6YK7?^g9_@ zyD(LriRv&Z{ut{_crar4MZGw@r3QoV?t$OfpSCj`Wy!G#fWvnb)6$N3jL8@9!x{sF zW*65p)M`W4;A=?SmkwwEOsu(%Z>4$%ZxTt^R!;@yXLjm344I?&(1fY*up@_FXw+oU{WVxGyDXOx#&2H!$c`?AhT_zx``kg^80 zrdBkq8*oqR%{l|Z@PWXYZvHo*549oZKu4eBnOr9b`!0d~ER#I=Nt2lW$ujG*j9P7z zZopS4N>xL>S!b!Is+z!Q%D{i1prUUFJX2+lMfR9wk4g3zxgCAG8w?Cl6|(^*-%O1r z-8@X}y}SZcV@FTG%CQ%bwqmdxmSY7-T_I{0;PT6WlW(Rrr{I7;YXHCCaMnRJBmfkB zM)sVQZYJH3Uslw@Ga$e0?THtPq7jR^C5eL5F9CL=nm6>QrdxK~4175Veecv4JKsCC zBBxuPaSEh_c3d#5i3@|1`Y^1Ta03tfFYo;k!Yh35kGKko-1jQ+o0v-IkNTJBXG$;@ zftuoy4#Q46`whL)dZnSJ7;#-YNtRa=|mCh1@Xq%Q4R0?{kwr0e@)4rN#hi(?Mm8=d?je!s?_ zX^dNcErwZ(lw(SJzj1=J+^Byw?ZZZs_pr{}nUMB!;~UAH*n)zvT;@G!!a7Kh+T|D2 z!wYy5q((+>o|J{(ox;-YK1!gJM9FT$Z&kf_!aJZ-#Xo2zi-i7|d~_cA1SP=i0LdW}iAEAAo*%d*Zr_VRiLbj$qA^e4&{C zP9&Sa-Q;3=IDH#}F>h5j%mntlyywhfmE?5}3n3Mj{AS4;_Fty;{*p0Sc292Wc29Wk z#QsyLVsLe_KWmr_Zwq>Z=VG#FSdL9cIC%&bUG-Ch4=wle31BhwuYfu`$&VhfW>%96}*z0Uu~SfN62wkhjYgSf>T;fi+rib)bZT z;rL#}2~$TY;X91bNPG!e$@suEBTMe4k~^tnyC~`Rw_rF6{QbvRq4+x}_@)S+q~IS! za1RAfhEk7)g1;6)8vx`PCm;vE-#Tho5Wg)RdD3>;or6eXP`$U)EG@o>NEA`^6j4RL zG84{^A7hO`t?++dT08{!7Xj6g@Gath1pEm3tMCsKd>_HzHv%3Tv+|7HXl4cfGt%M? z;BO@O^8~+U1U#&pL(1d%{K+4Jeq8QW%9*6aXHo7pD!1c4l)F(aCuQO48o_%?j9ZKhl~TXo;LZ)3(6kVT4{qs3YmyC~#CM;LqC=NCmtfZrEOZ~A()nX3>VIB{I@hYkQ9N^JZ;~xtvDnpY7X?r@}VEzpHU{Gb~x05;^ zAxZoOp`gn6^BxHdKTb@a2<#;7%zG*j2g3Y$dsRBLfYNZgrNz5|dTTgpH=$m>3#co@ zQHweVU*99ZCk_(=zoKB??!fEmLa2xGDijN-y^`hbvf$s{aGlBogXey4+62Kd8@SO(xL+?d>E8wD6CqGhVg&qD&SU1NcUQFCzFg zBj6=}AI7i1vwP8UBIxak4;%`Mr3M~F%rl?Er#;$j=G0OVN-0{>KPlGXP+D zMfhG(P6Vq$!Ihz)13@93ZF^Bx+D-#AhPGIncV^~iZOgIG$R3SwD?bJiUr}@eATF@RitOZvG4kOeKsKgj7XVJu;rVpC$SZ26mFK*i~l`2aH{_EckpK2fK=~dkLYA1B8%HsUr}= z&IfV?9v4al<_Xj-fmq<9k!~{K6`v%F22sYrN)(|Dm z){iBH|KFjBl%B9{lc-UZ!K*l7^mbxeK(nTPL^^g-jn5#Y>2qr!hw2gEPY33SD)$6p zMU{I3lc~x!LKdp^hZF_>ErFN=RI74w&O32}LWBRFz^NZ7(l{afjzFLA_XIjszXQG^ z9g>T*eL3DB?<;~;5 zo4B7bD_jCxHr#T!M!0QoyWxHY*9Uh7ZV1i<98aMfJ(DM~mLE=JEq|WCT7H2ajay-1 zEj4g2PGv1sF{}l@)YwvmcqQCAz-}1NTK+bLwL}5-jyTrBuVO8K)w7mUNP7-xuOdGh zWd_WwWgGHeLV64Q1Hjb@_xC8)k^#5Mz*?rD&KWw^@&V!#;TwTx8q$YQ=2rO61IN$c zPe9%-&}c^eZ=j3=;XlA{0o+!&4RHACzhy1({}5+Cw+HSUa9iN&;O>IUgu4aqMmQ^69Na{>D7azN@dn%}xPG`}ppBja z_*H6{;&aOE><%m^JDcom2+oSI_N*)qg`Z2km`UkTa5-?zI~2F3ywX};TU%dI?yj!K zacd@XvRXJd96d`|Ju5(X9sKobS~kLstOBvfoMKjvvWzW&qssE&3gJv%J2S!=;27NS zHai=D>xX*=t`kRWRBpSl}k#gtX7W#lc+*XM?*DE(wkS_7?c*a0}pa;FiD@!QBP78mFMQa9iM7;kLu=g!>NM9=M0$4#52k?kTumz;(hMf$N8R5$+V+@8QnEy$Lr2 zcLDAq96i~PZ?$NhN@hh1)#F`(cb#aNMxK6y7r|+ms*z86QIC2^cPdeT2EtTEfvtp3Sga_iP}Bs?Xuv;3%35i%A(0d` z{S&w}%Ko{sq1K@CrEv6IPM-g&6xTrxBwZ^cK{mD#ElhotG>`P~@-42=^{>fUX+h%i zHkH#zeAM6Eps*3`rt}1|DoyxT4s8s#0VC~6cu#H=R-jI35b11rm~IdkimsDJD3)Sm zzGj;mP_hw@XdwAee^G1{(RnY*E1F6;i4Pj3h*m|1seVN_vk_8ysHTn7zsXXO=4$j@ zS{q@~EWt<`i3e&$YB936WF^URT-v`DvPG~Ug(@zsr%}CGtXa_908CoXp*~ds+Q=>v z^_uM__yR$5q&%%`K4qZ}8Vf>nl#AX*R8=4~grB%{)4d}+R~oCYls~Jqsov9A;VkvI zt7}U$%iZM*JXKZB##t2&4TL{a)K!i-f(LS}0zcG(n%-9l3$g)lR=C^nx`Dz8N*}xp z;Tb4LPpPV_g}|VUNsHJ`Y$?i-ovQ;sG=`8Rp&FF&L=?npq+BjnvI&w}3tS`}l0NlD zdXr97i=L|JN=9yWK~~|yrG)=V@`?W@=4)#(7kLtQi{R*?`Pe`E&?T$};IsijL1AX$ z)Q6tAwRq#vJNG^Q;g2RfbB2Rn5^Agmu zlt*P#5!di(@Z_hXd{G!I)nAEtLl`{4j8=bmIl@&6xV>Sx^hi61c;^Uw1V{DI6Haq@ zS<2f4xS=q7<0$bPm`}o`z`5ZL!f`ly2;LwR*-;rwuv)1Gje4bVsJA-opEH5q8>#Grl3&2gQVGO@i%(lWEfP3^Ftg&kGH?85O ztz&Er+-|r&xM8@YI>x>QcNFe@xEtyL1NSgo0B#zrSSOtEUdGnK{S@xDM#dh4TaH2W z`*0&4qJ6c?&Zdw1_B4`*d8d6Oh=@}j!R`bsJ-gvk+J5*X|DUNiNDtDlfMY<#!PPQU zzU33j5N#6xbLtadh&FSz9Z*;v!6(|jtoo}|e>QxguSWILRsRn7^c27+JO|-x^00nO z$rAu!FTzjHkl8N$QoxZ8*TQdydqgcq{gocVOMU{JeVUzu!hVn+o_8Y7PDw%Aq~)2A z*0DyRZy=1mPPHB?6aHxJWv&VFa_$pgsjOM$CE-4$O;g}&e2^WbC!C(0U$8SJRb;UT z;*}WHO>IiP130uYh%!6q(c&ayl9?MY1iKo3_(N&oDfe8X#KQ3(9D%=K6#OSu{GC_8 z|6N-oKd*mDksaC2P2W~zQUpKzAzC!2@oWVQ@kNgGjq=S%Bfj^7zAHT>m(KyHZ$w>M zIl@P{PN=v}!KbtVHO}Ev-S5Ju`ae|tFWiH@2E^B?{zlbrQ~eq62|gMAB=~7+{9CI3 z1Jz%o`X%tqfGdMfGz&THvLofvN27TFu=H^Flr{vPO_+gPY5+T*s0)G!2JxSn=9%@7KUjP z_|z7i@Ci4;rd$E{P#Em$E5KUoLwI*w0oDOndRC}qh~ADXl<}!$TEfblyF!^0DlGA8 z#Z|h|9!-wsh7kR^p8!j;qyDq!6JSWrcHpDuPWV*E3i!n5TKLr88dZOn>OZ9VKT-Yv zg-?%ps^TX-p!OX#eHcE~!|qk+CYqnUI>>5$8R^Lt)-){JnwpaBL6mjzD(d>~6 zSI`Kz5orkskM?M|^^HpYfQpmDCtluF{SQ@tI^ak)H^QgBHwQk!xq)A^dF_wek-{SE z8reLJ&!?_H%TAS+2KYo@vsy=s>bJuuTr^HFT$>y13G&sXO0_*PVIzGG*CUuKY<2hvyVv!@wBQw#TS&@SvDMnFRZq+iE5bO z`orM1#*ihnJ`P*h)Kj!SbiV%C|ewZg6oA3J~#H{19WgTcI=X zwP!uTrQ4A911$yTgp}5Twht@Yxex0bls&m81b6!*Sf>E4KP)X5X$)!Bu(IcV!dMZ? zj`mCy&|_zrV`rJrBk0(_>nt;Yl`L_(?{K;o*4Ncxp9+VOB{)HJ6wIout!3Y5g*n;P zwa%pt&PH)iiM&a=LYK3)HmliL;c+`l0FjlOn><&CS`3RaRZM zzPz@&vaF$;inyJPWp(w{byf9jB_l*duEzQeSsEYc*_Ww1V?lA!Y&#fZbEvL$_3NFe zU^}QOZ(Lh}T@8dbAh@30MEMmB8yUL?wY%%f+4Y99^-UWZ8msHvRb>@rwK&TKl@(2O z)fMX+Se&k`d|lJpGG}u&=>LfDfPr=8)pbAz9&XZ=RVaC1)hR%0S=sW61~-}x?68*& zO73jJxvsLdj!h)4tKG<4$0misWllC9xa#Yws@J+*jn48)I-ss~vH=IiYgPS z)AeO)N`IyO~kK>a;Vw2r%p&f*sc8MqtkYpWVaM^e~) zK_O&TRbN(#vrB|PU0r!?85G7M7GdzB9<)ymPR+|SmRl{7E0x+ufGR#aUJ?v zZh4bCtFf^jQmkeRA!oM}+r$;lrY7{Sa)O{vm*I9dR(ZbsLCRM$m zW0}tN)fLV~_2|SzHoGBe5fMmIaaKZi!Sh-tE7lj*I-L#d%i*~T>(@c?ok1YhLG>59 z%NyOEhDBhwgzb;Y_c$9jI-HGF^=SXP3g^Olk7&a`5NuI(#XTYcr?V7a=xJ6k=FDoIJHR#vzAz=Usk8ICh@4) z8CB)kVtt|0ec9}}QHz=~)rN6kQy%29JF^OwWNC(sEnJ$nuqYU-*TLS*x2-8}s;($& zLbKGZwP!AyZ-ZbP%iN9S)$S&HreflX=iAhK9rcY!n_~~Re42$=83o(+FXJOEyL%K& zWhEm>D=Qg=HYA1<2W0C%6E0!BVFb(d^XIP^!QnqcS4IZO477sR`$wVgpTS8(i0`w) z6*T+*iYM)M z=9{x<0oyrgev@Yn%;mp~Eq_7Q{EXt+bL@+#$)5g9dz#8PEBj0>R;d53{!H;|)Y4qZ zq0W^?TR)DajY(ik0%H>Re@Oz5ZL>4$4!dA^7RC$Qu`UyHab@-!k*6#MgBueOdA0Rx zG0n#!7c+RVZ2^qocx#o8sT6yy6 zj%IkA!r^!qU>IZFBjBfcvLTPSP%OhABNCVhCcbl@>Yp%V28 z#}hYNI^YvVOGm-f%cSRACVe^5U5DZH`2I<$n4Nf*F+1qg;^to5u?B5g+}OuhPguMk zaa>3V=Eohyw=rSynTX?e4}$p#{rHX|4F9g@ahe~VkFumYRyZ9at-%BOMn1GxamjP^ zgFpTLB7Q3I$`5{@mi=KBJC($a$E|t`Kk|FoXV#zZDR}dHj=oIRH;Mgzapur>{^w8M zpK>&j{b9w%xA41v5zpsduVgPws~mdd`a@Cd$Bez6*Y(aZ3OtSQ9qhzJ*IW3hPW3tS z(Oc~I^VsV-6&HTCrf&&*kY)UCrRxxYFxkaz8Me-Z+#|xgH z$)04=RZ`N13H7yb+I$3B*qyzzfK|Nik0 zF31NycyZDnv)N12`#yL>{@`tSACp^|yq3vDOx`oYT zm+!we{!e*C51yf4%e!ut+hgQkZI#hR6-@5)%3o#jA^R==C$m#BdtIJ6pn!_^M<2<( z^W@(Z$uBLIH#4~$f5_lI`MwyrjLEM(%zh}dhh&D|OALR&!grbcVwhb=?ee@|(uP>*8G1o}gvsA$@_NYXf8?Wc<^4=+-<1{5Hux=J5Hv7EITG*{@CIPjU@lj)6*qS13t0M=RrwBoHk-wgBJeSRZ`ufa z%5NFacj#+DFL9Ru{%(Lj2j!F4%@H_sOz(ICBg`CFBpq^y=B4aT0TYs+fkA#d6%+M4 zopLR8q{K*yBp>=60ju~{m%i&I`F?sSQnfnA3v3(cnS2R7f++Eda!g~NXAkR)EC8Zp zd0SZB%D3*yx8-x$T+kB!n*t#mXfxeD$JOl17!wQGn*xtlWQX|1cSg_~m~K3sx?B>_ zC7NQz7vb7B>4o4{`RW+gzR=1Tg+GPv0Lbxm-L1Mg_=;y_8&Hk(=!{z(i6@cDT@WYOI1i}DWX@CBgN>~9&mr}lA09j8cw&;ZVuq8 zhaBnGB-~+~kKS%6LcWn@MoM|4j59%x4lPc@sWDS{K~0aB1$+f_b@%?#L;+qabSqpaEZ$ zu^>0gTCym!C}-a6q@)>yqo_cEWu{Yo z(-(N$?)ti=cTT^3>7D5}EfAo$r!Nqwl-MU0Tu65?Yp36?_(24q=BA=ds{AIEs)8_L ztvOjl+q_$Ejv#np){>&E0&B5C{(_~TIu8IvOEZ_=W_8r!*v^Uz!E~#%$%>1=&RLh# za+44X%1{o8R8Syf?8rzjZ)$R`TT{Ey+Pto|u4&$MPh;I}O%*QZy7H#4tgEhQtZ%BX za(@Lk(rznnS~qL`?CDnAFs-g~Ho1$PjZHY9oi}~GMtCC`~|t;1dKdDHVW3T74K=K8FK1x3@X>$Q4Q zXC=+LIq8-p_|vTo$_3r}#*D@aSG5~wuAW99XimKad7d@3)fIO-Hx}U% z&D`?Y&e^HX" +// For error or control response then return a string :- +// Error - "1," +// FileNotFound - "2," +// AccessDenied - "3," +// BadParameter - "4, +// NotWorkingCopy - "5," +// NoSuchAction - "6, +// LaunchURL - "7," +// CommandLine - "8," + +var response = "7," + runAction(); +response; diff --git a/config/alfresco/desktop/urlLink.js b/config/alfresco/desktop/urlLink.js new file mode 100644 index 0000000000..f498fb1549 --- /dev/null +++ b/config/alfresco/desktop/urlLink.js @@ -0,0 +1,29 @@ +// Main action + +function runAction() +{ + out.println("URL link to " + deskParams.getFolder()); + + var urlStr = webURL + "navigate/browse/workspace/SpacesStore/" + deskParams.getFolderNode().getId() + + "?ticket=" + deskParams.getTicket(); + out.println( " url=" + urlStr); + + return urlStr; +} + +// Run the action +// +// Response :- +// Success - no return or return 0, or "0," +// For error or control response then return a string :- +// Error - "1," +// FileNotFound - "2," +// AccessDenied - "3," +// BadParameter - "4, +// NotWorkingCopy - "5," +// NoSuchAction - "6, +// LaunchURL - "7," +// CommandLine - "8," + +var response = "7," + runAction(); +response; diff --git a/config/alfresco/domain/hibernate-cfg.properties b/config/alfresco/domain/hibernate-cfg.properties index 68da4ebd1c..5da910a9cd 100644 --- a/config/alfresco/domain/hibernate-cfg.properties +++ b/config/alfresco/domain/hibernate-cfg.properties @@ -1,16 +1,15 @@ -# -# Hibernate configuration -# -hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect - -hibernate.jdbc.use_streams_for_binary=true -hibernate.hbm2ddl.auto=update -hibernate.show_sql=false -hibernate.cache.use_query_cache=true -hibernate.max_fetch_depth=10 -hibernate.cache.provider_class=org.alfresco.repo.cache.InternalEhCacheManagerFactoryBean -hibernate.cache.use_second_level_cache=true -hibernate.default_batch_fetch_size=1 -hibernate.jdbc.batch_size=32 -hibernate.connection.release_mode=auto +# +# Hibernate configuration +# +hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect + +hibernate.jdbc.use_streams_for_binary=true +hibernate.show_sql=false +hibernate.cache.use_query_cache=true +hibernate.max_fetch_depth=10 +hibernate.cache.provider_class=org.alfresco.repo.cache.InternalEhCacheManagerFactoryBean +hibernate.cache.use_second_level_cache=true +hibernate.default_batch_fetch_size=1 +hibernate.jdbc.batch_size=32 +hibernate.connection.release_mode=auto hibernate.connection.isolation=4 \ No newline at end of file diff --git a/config/alfresco/ehcache-default.xml b/config/alfresco/ehcache-default.xml index a280c23786..df55450b15 100644 --- a/config/alfresco/ehcache-default.xml +++ b/config/alfresco/ehcache-default.xml @@ -1,184 +1,184 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/extension/custom-connection-pool-context.xml.sample b/config/alfresco/extension/custom-connection-pool-context.xml.sample new file mode 100644 index 0000000000..546c1d3ac1 --- /dev/null +++ b/config/alfresco/extension/custom-connection-pool-context.xml.sample @@ -0,0 +1,57 @@ + + + + + + + + + + + ${db.driver} + + + ${db.url} + + + ${db.username} + + + ${db.password} + + + false + + + + ${db.pool.initial} + + + ${db.pool.max} + + + 300000 + + + -1 + + + false + + + 50000 + + + true + + + select 1 + + + + diff --git a/config/alfresco/extension/custom-db-connection.properties.sample b/config/alfresco/extension/custom-db-connection.properties.sample index 175328c81f..de46bd2233 100644 --- a/config/alfresco/extension/custom-db-connection.properties.sample +++ b/config/alfresco/extension/custom-db-connection.properties.sample @@ -9,7 +9,13 @@ #db.pool.max=100 # -# MySQL connection (This is default and requires mysql-connector-java-3.1.12-bin.jar, which ships with the Alfresco server) +# HSQL connection +# +#db.driver=org.hsqldb.jdbcDriver +#db.url=jdbc:hsqldb:file:alf_data/hsql_data/alfresco;ifexists=true;shutdown=true; + +# +# MySQL connection (This is default and requires mysql-connector-java-5.0.3-bin.jar, which ships with the Alfresco server) # #db.driver=org.gjt.mm.mysql.Driver #db.url=jdbc:mysql://localhost/alfresco diff --git a/config/alfresco/extension/custom-hibernate-dialect.properties.sample b/config/alfresco/extension/custom-hibernate-dialect.properties.sample index 611a40ca78..e3563689d5 100644 --- a/config/alfresco/extension/custom-hibernate-dialect.properties.sample +++ b/config/alfresco/extension/custom-hibernate-dialect.properties.sample @@ -7,6 +7,11 @@ # For a full list: http://www.hibernate.org/hib_docs/v3/reference/en/html_single/#configuration-optional-dialects # +# +# HSQL dialect +# +#hibernate.dialect=org.hibernate.dialect.HSQLDialect + # # MySQL dialect (default) # diff --git a/config/alfresco/extension/file-servers-custom.xml b/config/alfresco/extension/file-servers-custom.xml new file mode 100644 index 0000000000..95bf3f6d5f --- /dev/null +++ b/config/alfresco/extension/file-servers-custom.xml @@ -0,0 +1,55 @@ + + + + + + + + + + workspace://SpacesStore + /app:company_home + + + + __Alfresco.url + http://${localname}:8080/alfresco/ + + + + + + + + + + alfresco/desktop/Alfresco.exe + http://${localname}:8080/alfresco/ + + + org.alfresco.filesys.smb.server.repo.desk.CheckInOutDesktopAction + CheckInOut + __CheckInOut.exe + + + org.alfresco.filesys.smb.server.repo.desk.JavaScriptDesktopAction + JavaScriptURL + __ShowDetails.exe + + anyFiles + copyToTarget + + + + + + + + + + diff --git a/config/alfresco/extension/index-tracking-context.xml.sample b/config/alfresco/extension/index-tracking-context.xml.sample new file mode 100644 index 0000000000..5c1343bec2 --- /dev/null +++ b/config/alfresco/extension/index-tracking-context.xml.sample @@ -0,0 +1,71 @@ + + + + + + + + + + + org.alfresco.repo.node.index.IndexRecoveryJob + + + + + + + + + + + + + + + 0,30 * * * * ? + + + + + + true + + + + + + + + + + org.alfresco.repo.node.index.IndexRecoveryJob + + + + + + + + + + + + + + + + 5 + + + 0 + + + + diff --git a/config/alfresco/file-servers.xml b/config/alfresco/file-servers.xml index e26cd80af5..b1583473fc 100644 --- a/config/alfresco/file-servers.xml +++ b/config/alfresco/file-servers.xml @@ -38,25 +38,28 @@ __AlfrescoClient.url - http://localhost:8080/alfresco/ + http://${localname}:8080/alfresco/ - - - - - - + + + + + + + + + diff --git a/config/alfresco/hibernate-context.xml b/config/alfresco/hibernate-context.xml index 115539c043..095330aa22 100644 --- a/config/alfresco/hibernate-context.xml +++ b/config/alfresco/hibernate-context.xml @@ -1,277 +1,277 @@ - - - - - - - - - - classpath:alfresco/domain/hibernate-cfg.properties - - - - - - - true - - - - classpath:alfresco/domain/cache-strategies.properties - - - - - - - - - - - - - true - - - - - - - - org/alfresco/repo/domain/hibernate/Node.hbm.xml - org/alfresco/repo/domain/hibernate/Store.hbm.xml - org/alfresco/repo/domain/hibernate/Transaction.hbm.xml - org/alfresco/repo/domain/hibernate/VersionCount.hbm.xml - org/alfresco/repo/domain/hibernate/AppliedPatch.hbm.xml - org/alfresco/repo/domain/hibernate/Permission.hbm.xml - org/alfresco/repo/avm/hibernate/AVM.hbm.xml - - - - org/alfresco/repo/audit/hibernate/Audit.hbm.xml - - - - - - - - org/jbpm/graph/action/Script.hbm.xml - org/jbpm/db/hibernate.queries.hbm.xml - org/jbpm/graph/def/ProcessDefinition.hbm.xml - org/jbpm/graph/def/Node.hbm.xml - org/jbpm/graph/def/Transition.hbm.xml - org/jbpm/graph/def/Event.hbm.xml - org/jbpm/graph/def/Action.hbm.xml - org/jbpm/graph/def/SuperState.hbm.xml - org/jbpm/graph/def/ExceptionHandler.hbm.xml - org/jbpm/instantiation/Delegation.hbm.xml - org/jbpm/graph/node/StartState.hbm.xml - org/jbpm/graph/node/EndState.hbm.xml - org/jbpm/graph/node/ProcessState.hbm.xml - org/jbpm/graph/node/Decision.hbm.xml - org/jbpm/graph/node/Fork.hbm.xml - org/jbpm/graph/node/Join.hbm.xml - org/jbpm/graph/node/State.hbm.xml - org/jbpm/graph/node/TaskNode.hbm.xml - org/jbpm/context/def/ContextDefinition.hbm.xml - org/jbpm/context/def/VariableAccess.hbm.xml - org/jbpm/taskmgmt/def/TaskMgmtDefinition.hbm.xml - org/jbpm/taskmgmt/def/Swimlane.hbm.xml - org/jbpm/taskmgmt/def/Task.hbm.xml - org/jbpm/taskmgmt/def/TaskController.hbm.xml - org/jbpm/module/def/ModuleDefinition.hbm.xml - org/jbpm/bytes/ByteArray.hbm.xml - org/jbpm/file/def/FileDefinition.hbm.xml - org/jbpm/scheduler/def/CreateTimerAction.hbm.xml - org/jbpm/scheduler/def/CancelTimerAction.hbm.xml - org/jbpm/graph/exe/Comment.hbm.xml - org/jbpm/graph/exe/ProcessInstance.hbm.xml - org/jbpm/graph/exe/Token.hbm.xml - org/jbpm/graph/exe/RuntimeAction.hbm.xml - org/jbpm/module/exe/ModuleInstance.hbm.xml - org/jbpm/context/exe/ContextInstance.hbm.xml - org/jbpm/context/exe/TokenVariableMap.hbm.xml - org/jbpm/context/exe/VariableInstance.hbm.xml - org/jbpm/context/exe/variableinstance/ByteArrayInstance.hbm.xml - org/jbpm/context/exe/variableinstance/DateInstance.hbm.xml - org/jbpm/context/exe/variableinstance/DoubleInstance.hbm.xml - org/jbpm/context/exe/variableinstance/HibernateLongInstance.hbm.xml - org/jbpm/context/exe/variableinstance/HibernateStringInstance.hbm.xml - org/jbpm/context/exe/variableinstance/LongInstance.hbm.xml - org/jbpm/context/exe/variableinstance/NullInstance.hbm.xml - org/jbpm/context/exe/variableinstance/StringInstance.hbm.xml - org/jbpm/msg/Message.hbm.xml - org/jbpm/msg/db/TextMessage.hbm.xml - org/jbpm/command/ExecuteActionCommand.hbm.xml - org/jbpm/command/ExecuteNodeCommand.hbm.xml - org/jbpm/command/SignalCommand.hbm.xml - org/jbpm/command/TaskInstanceEndCommand.hbm.xml - org/jbpm/taskmgmt/exe/TaskMgmtInstance.hbm.xml - org/jbpm/taskmgmt/exe/TaskInstance.hbm.xml - org/jbpm/taskmgmt/exe/PooledActor.hbm.xml - org/jbpm/taskmgmt/exe/SwimlaneInstance.hbm.xml - org/jbpm/scheduler/exe/Timer.hbm.xml - org/jbpm/logging/log/ProcessLog.hbm.xml - org/jbpm/logging/log/MessageLog.hbm.xml - org/jbpm/logging/log/CompositeLog.hbm.xml - org/jbpm/graph/log/ActionLog.hbm.xml - org/jbpm/graph/log/NodeLog.hbm.xml - org/jbpm/graph/log/ProcessInstanceCreateLog.hbm.xml - org/jbpm/graph/log/ProcessInstanceEndLog.hbm.xml - org/jbpm/graph/log/SignalLog.hbm.xml - org/jbpm/graph/log/TokenCreateLog.hbm.xml - org/jbpm/graph/log/TokenEndLog.hbm.xml - org/jbpm/graph/log/TransitionLog.hbm.xml - org/jbpm/context/log/VariableLog.hbm.xml - org/jbpm/context/log/VariableCreateLog.hbm.xml - org/jbpm/context/log/VariableDeleteLog.hbm.xml - org/jbpm/context/log/VariableUpdateLog.hbm.xml - org/jbpm/context/log/variableinstance/ByteArrayUpdateLog.hbm.xml - org/jbpm/context/log/variableinstance/DateUpdateLog.hbm.xml - org/jbpm/context/log/variableinstance/DoubleUpdateLog.hbm.xml - org/jbpm/context/log/variableinstance/HibernateLongUpdateLog.hbm.xml - org/jbpm/context/log/variableinstance/HibernateStringUpdateLog.hbm.xml - org/jbpm/context/log/variableinstance/LongUpdateLog.hbm.xml - org/jbpm/context/log/variableinstance/StringUpdateLog.hbm.xml - org/jbpm/taskmgmt/log/TaskLog.hbm.xml - org/jbpm/taskmgmt/log/TaskCreateLog.hbm.xml - org/jbpm/taskmgmt/log/TaskAssignLog.hbm.xml - org/jbpm/taskmgmt/log/TaskEndLog.hbm.xml - org/jbpm/taskmgmt/log/SwimlaneLog.hbm.xml - org/jbpm/taskmgmt/log/SwimlaneCreateLog.hbm.xml - org/jbpm/taskmgmt/log/SwimlaneAssignLog.hbm.xml - - - org/alfresco/repo/workflow/jbpm/WorkflowTaskInstance.hbm.xml - - - - - - ${cache.strategy} - ${cache.strategy} - ${cache.strategy} - ${cache.strategy} - ${cache.strategy} - ${cache.strategy} - ${cache.strategy} - ${cache.strategy} - ${cache.strategy} - - ${cache.strategy} - ${cache.strategy} - ${cache.strategy} - ${cache.strategy} - - ${cache.strategy} - ${cache.strategy} - ${cache.strategy} - - - - - ${cache.strategy} - ${cache.strategy} - ${cache.strategy} - - ${cache.strategy} - ${cache.strategy} - - - - - - - - SYNCHRONIZATION_ALWAYS - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - org.alfresco.repo.node.db.NodeDaoService - - - - - - - dbNodeDaoServiceTxnRegistration - - - - - - - - - - - - - - + + + + + + + + + + classpath:alfresco/domain/hibernate-cfg.properties + + + + + + + true + + + + classpath:alfresco/domain/cache-strategies.properties + + + + + + + + + + + + + true + + + + + + + + org/alfresco/repo/domain/hibernate/Node.hbm.xml + org/alfresco/repo/domain/hibernate/Store.hbm.xml + org/alfresco/repo/domain/hibernate/Transaction.hbm.xml + org/alfresco/repo/domain/hibernate/VersionCount.hbm.xml + org/alfresco/repo/domain/hibernate/AppliedPatch.hbm.xml + org/alfresco/repo/domain/hibernate/Permission.hbm.xml + org/alfresco/repo/avm/hibernate/AVM.hbm.xml + + + + org/alfresco/repo/audit/hibernate/Audit.hbm.xml + + + + + + + + org/jbpm/graph/action/Script.hbm.xml + org/jbpm/db/hibernate.queries.hbm.xml + org/jbpm/graph/def/ProcessDefinition.hbm.xml + org/jbpm/graph/def/Node.hbm.xml + org/jbpm/graph/def/Transition.hbm.xml + org/jbpm/graph/def/Event.hbm.xml + org/jbpm/graph/def/Action.hbm.xml + org/jbpm/graph/def/SuperState.hbm.xml + org/jbpm/graph/def/ExceptionHandler.hbm.xml + org/jbpm/instantiation/Delegation.hbm.xml + org/jbpm/graph/node/StartState.hbm.xml + org/jbpm/graph/node/EndState.hbm.xml + org/jbpm/graph/node/ProcessState.hbm.xml + org/jbpm/graph/node/Decision.hbm.xml + org/jbpm/graph/node/Fork.hbm.xml + org/jbpm/graph/node/Join.hbm.xml + org/jbpm/graph/node/State.hbm.xml + org/jbpm/graph/node/TaskNode.hbm.xml + org/jbpm/context/def/ContextDefinition.hbm.xml + org/jbpm/context/def/VariableAccess.hbm.xml + org/jbpm/taskmgmt/def/TaskMgmtDefinition.hbm.xml + org/jbpm/taskmgmt/def/Swimlane.hbm.xml + org/jbpm/taskmgmt/def/Task.hbm.xml + org/jbpm/taskmgmt/def/TaskController.hbm.xml + org/jbpm/module/def/ModuleDefinition.hbm.xml + org/jbpm/bytes/ByteArray.hbm.xml + org/jbpm/file/def/FileDefinition.hbm.xml + org/jbpm/scheduler/def/CreateTimerAction.hbm.xml + org/jbpm/scheduler/def/CancelTimerAction.hbm.xml + org/jbpm/graph/exe/Comment.hbm.xml + org/jbpm/graph/exe/ProcessInstance.hbm.xml + org/jbpm/graph/exe/Token.hbm.xml + org/jbpm/graph/exe/RuntimeAction.hbm.xml + org/jbpm/module/exe/ModuleInstance.hbm.xml + org/jbpm/context/exe/ContextInstance.hbm.xml + org/jbpm/context/exe/TokenVariableMap.hbm.xml + org/jbpm/context/exe/VariableInstance.hbm.xml + org/jbpm/context/exe/variableinstance/ByteArrayInstance.hbm.xml + org/jbpm/context/exe/variableinstance/DateInstance.hbm.xml + org/jbpm/context/exe/variableinstance/DoubleInstance.hbm.xml + org/jbpm/context/exe/variableinstance/HibernateLongInstance.hbm.xml + org/jbpm/context/exe/variableinstance/HibernateStringInstance.hbm.xml + org/jbpm/context/exe/variableinstance/LongInstance.hbm.xml + org/jbpm/context/exe/variableinstance/NullInstance.hbm.xml + org/jbpm/context/exe/variableinstance/StringInstance.hbm.xml + org/jbpm/msg/Message.hbm.xml + org/jbpm/msg/db/TextMessage.hbm.xml + org/jbpm/command/ExecuteActionCommand.hbm.xml + org/jbpm/command/ExecuteNodeCommand.hbm.xml + org/jbpm/command/SignalCommand.hbm.xml + org/jbpm/command/TaskInstanceEndCommand.hbm.xml + org/jbpm/taskmgmt/exe/TaskMgmtInstance.hbm.xml + org/jbpm/taskmgmt/exe/TaskInstance.hbm.xml + org/jbpm/taskmgmt/exe/PooledActor.hbm.xml + org/jbpm/taskmgmt/exe/SwimlaneInstance.hbm.xml + org/jbpm/scheduler/exe/Timer.hbm.xml + org/jbpm/logging/log/ProcessLog.hbm.xml + org/jbpm/logging/log/MessageLog.hbm.xml + org/jbpm/logging/log/CompositeLog.hbm.xml + org/jbpm/graph/log/ActionLog.hbm.xml + org/jbpm/graph/log/NodeLog.hbm.xml + org/jbpm/graph/log/ProcessInstanceCreateLog.hbm.xml + org/jbpm/graph/log/ProcessInstanceEndLog.hbm.xml + org/jbpm/graph/log/SignalLog.hbm.xml + org/jbpm/graph/log/TokenCreateLog.hbm.xml + org/jbpm/graph/log/TokenEndLog.hbm.xml + org/jbpm/graph/log/TransitionLog.hbm.xml + org/jbpm/context/log/VariableLog.hbm.xml + org/jbpm/context/log/VariableCreateLog.hbm.xml + org/jbpm/context/log/VariableDeleteLog.hbm.xml + org/jbpm/context/log/VariableUpdateLog.hbm.xml + org/jbpm/context/log/variableinstance/ByteArrayUpdateLog.hbm.xml + org/jbpm/context/log/variableinstance/DateUpdateLog.hbm.xml + org/jbpm/context/log/variableinstance/DoubleUpdateLog.hbm.xml + org/jbpm/context/log/variableinstance/HibernateLongUpdateLog.hbm.xml + org/jbpm/context/log/variableinstance/HibernateStringUpdateLog.hbm.xml + org/jbpm/context/log/variableinstance/LongUpdateLog.hbm.xml + org/jbpm/context/log/variableinstance/StringUpdateLog.hbm.xml + org/jbpm/taskmgmt/log/TaskLog.hbm.xml + org/jbpm/taskmgmt/log/TaskCreateLog.hbm.xml + org/jbpm/taskmgmt/log/TaskAssignLog.hbm.xml + org/jbpm/taskmgmt/log/TaskEndLog.hbm.xml + org/jbpm/taskmgmt/log/SwimlaneLog.hbm.xml + org/jbpm/taskmgmt/log/SwimlaneCreateLog.hbm.xml + org/jbpm/taskmgmt/log/SwimlaneAssignLog.hbm.xml + + + org/alfresco/repo/workflow/jbpm/WorkflowTaskInstance.hbm.xml + + + + + + ${cache.strategy} + ${cache.strategy} + ${cache.strategy} + ${cache.strategy} + ${cache.strategy} + ${cache.strategy} + ${cache.strategy} + ${cache.strategy} + ${cache.strategy} + + ${cache.strategy} + ${cache.strategy} + ${cache.strategy} + ${cache.strategy} + + ${cache.strategy} + ${cache.strategy} + ${cache.strategy} + + + + + ${cache.strategy} + ${cache.strategy} + ${cache.strategy} + + ${cache.strategy} + ${cache.strategy} + + + + + + + + SYNCHRONIZATION_ALWAYS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.alfresco.repo.node.db.NodeDaoService + + + + + + + dbNodeDaoServiceTxnRegistration + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/import-export-context.xml b/config/alfresco/import-export-context.xml index 6b09562fe9..7dacdad59a 100644 --- a/config/alfresco/import-export-context.xml +++ b/config/alfresco/import-export-context.xml @@ -171,8 +171,8 @@ - - + + @@ -227,6 +227,9 @@ --> + + + diff --git a/config/alfresco/messages/bpm-messages.properties b/config/alfresco/messages/bpm-messages.properties index 69419ae91b..ddfee4d3d2 100644 --- a/config/alfresco/messages/bpm-messages.properties +++ b/config/alfresco/messages/bpm-messages.properties @@ -4,8 +4,8 @@ bpm_businessprocessmodel.title=Business Process Model bpm_businessprocessmodel.description=Base definitions of all Business Processes # Default transition -bpm_businessprocessmodel.transition.title=Done -bpm_businessprocessmodel.transition.description=Done +bpm_businessprocessmodel.transition.title=Task Done +bpm_businessprocessmodel.transition.description=Task Done # Base Task bpm_businessprocessmodel.type.bpm_task.title=Task diff --git a/config/alfresco/messages/copy-service.properties b/config/alfresco/messages/copy-service.properties new file mode 100644 index 0000000000..ce041e4bae --- /dev/null +++ b/config/alfresco/messages/copy-service.properties @@ -0,0 +1,3 @@ +# copy service externalised display strings + +copy_service.copy_of_label=Copy of {0} \ No newline at end of file diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties index c2a5bf70ef..aad05f496d 100644 --- a/config/alfresco/messages/patch-service.properties +++ b/config/alfresco/messages/patch-service.properties @@ -27,8 +27,8 @@ patch.savedSearchesPermission.description=Sets required permissions on 'Saved Se patch.savedSearchesPermission.result.applied=Granted CONTRIBUTOR role to EVERYONE on ''Saved Searches'' folder: {0}. patch.savedSearchesPermission.err.not_found='Saved Searches' folder could not be found. -patch.updatePermissionData.description=Update permission entries from 'folder' to 'cmobject'. -patch.updatePermissionData.upgrade=Please follow an upgrade path via server version 1.2.1 +patch.updatePermissionData.description=Update permissions from 'folder' to 'cmobject' [JIRA: AR-344]. +patch.updatePermissionData.result=Changed {0} 'folder' access control entries to 'cmobject'. patch.authoritiesFolder.description=Ensures the existence of the user authorities folder [JIRA: AR-497]. @@ -39,7 +39,7 @@ patch.fixNodeSerializableValues.description=Ensure that property values are not patch.fixNodeSerializableValues.result=Fixed {0} node property serialized values patch.updateGuestPermission.description=Rename guest permission from 'Guest' to 'Consumer' -patch.updateGuestPermission.upgrade=Please follow an upgrade path via server version 1.2.1 +patch.updateGuestPermission.result=Changed {0} 'Guest' access control entries to 'Consumer'. patch.categoryRootPermission.description=Sets required permissions on 'Category Root' folder. patch.categoryRootPermission.result=Granted CONSUMER role to GUEST on ''Category Root'' folder: {0}. @@ -52,7 +52,7 @@ patch.spacesRootPermission.description=Change Spaces store root permission from patch.spacesRootPermission.result=Updated Spaces store root permission from 'Consumer' to 'Read' patch.contentPermission.description=Update permission entries from 'cm:content' to 'sys:base'. -patch.contentPermission.upgrade=Please follow an upgrade path via server version 1.2.1 +patch.contentPermission.result=Changed {0} 'cm:content' access control entries to 'sys:base'. patch.forumsIcons.description=Updates forums icon references patch.forumsIcons.result=Updated {0} icon references @@ -96,6 +96,6 @@ patch.schemaUpgradeScript.description=Ensures that the database upgrade script h patch.schemaUpgradeScript.err.not_executed=The schema upgrade script, ''{0}'', has not been run against this database. patch.uniqueChildName.description=Checks and renames duplicate children. -patch.uniqueChildName.copyOf=({0}) +patch.uniqueChildName.copyOf=({0}-{1}) patch.uniqueChildName.result=Checked {0} associations and fixed {1} duplicates. See file {2} for details. - +patch.uniqueChildName.err.unable_to_fix=Auto-fixing of duplicate names failed. See file {0} for details. diff --git a/config/alfresco/messages/schema-update.properties b/config/alfresco/messages/schema-update.properties index 5cebba18a2..425124efa3 100644 --- a/config/alfresco/messages/schema-update.properties +++ b/config/alfresco/messages/schema-update.properties @@ -1,6 +1,8 @@ # Schema update messages schema.update.msg.executing_script=Executing database script: {0} +schema.update.msg.optional_statement_failed=Optional statement execution failed:\n SQL: {0}\n Error: {1}\n File: {2}\n Line: {3} +schema.update.err.statement_failed=Statement execution failed:\n SQL: {0}\n Error: {1}\n File: {2}\n Line: {3} schema.update.err.update_failed=Schema auto-update failed schema.update.err.validation_failed=Schema validation failed schema.update.err.update_script_not_run=The following schema upgrade script needs to be executed manually: {0} diff --git a/config/alfresco/messages/workflow-interpreter-help.properties b/config/alfresco/messages/workflow-interpreter-help.properties new file mode 100644 index 0000000000..398b1b772b --- /dev/null +++ b/config/alfresco/messages/workflow-interpreter-help.properties @@ -0,0 +1 @@ +workflow_console.help=alfresco/messages/workflow-interpreter-help.txt diff --git a/config/alfresco/messages/workflow-interpreter-help.txt b/config/alfresco/messages/workflow-interpreter-help.txt new file mode 100644 index 0000000000..d8ed5405e6 --- /dev/null +++ b/config/alfresco/messages/workflow-interpreter-help.txt @@ -0,0 +1,174 @@ +## +## Meta commands +## + +ok> help + + List this help. + +ok> r + + Repeat last command. + +ok> user [] + + Switch to specified . If is omitted, the currently + selected user is shown. + +ok> use + + Show current workflow context. + +## +## Workflow Definition Commands +## + +ok> deploy + + Deploy workflow definition to Alfresco server. + + class path to workflow definition. + +ok> redeploy + + Redeploy the last workflow definition. + +ok> show definitions + + List all deployed workflow definitions. + +ok> use definition [] + + Switch to use the workflow definition identified by . If + is ommited, the currently selected workflow definition + is shown. + +## +## Variable Commands +## + +ok> var + + Show all defined variables. + +ok> var [*]= + + Define or update a variable. + + variable name + [*] if specified, define a collection + variable value (comma-seperate to specify a list of values) + + e.g. + + set bpm:assignee*=admin,fred + set wf:notifyMe=true + +ok> var [*] person + + Define or update a (cm:person) node ref variable. + + variable name + [*] if specified, define a collection + variable value (comma-seperate to specify a list of values) + + e.g. + + set bpm:assignee* person admin,fred + +ok> var = + + Delete an existing variable. + + variable name + +## +## Workflow Commands +## + +ok> start []]* + + Start a new workflow using the currently selected workflow definition. Start + Task parameters are provided as name/value pairs or references to pre-defined + variables. + + e.g. + + start bpm:assignee=david wf:predefined + +ok> show workflows + + Display the list of active workflows for the currently selected workflow + definition. + +ok> use workflow + + Use the specified . + +ok> show paths [] + + Display the workflow paths for the specified . If + is omitted, the paths for the currently started workflow are shown. + +ok> show transitions [] + + Display all available transitions for the specified . If + is omitted, the transitions for the currently started workflow + are shown. + +ok> signal [] + + Signal transition on specified . If is omitted, the + default transition is taken. + +ok> desc workflow + + Describe the specified . + +ok> end workflow + + End (cancel) the specified . + +## +## Task Commands +## + +ok> show my tasks + + List tasks assigned to the currently selected user. + +ok> show my completed + + List tasks completed by the currently selected user. + +ok> show tasks [] + + List tasks associated with the specified workflow . If is + omitted, the tasks associated with the currently selected workflow path are + shown. + +ok> desc task + + Describe the task identified by . + +ok> update task []]* + + Update the state of the specified . Task properties are provided as + name/value pairs or references to pre-defined variables. + + variable name + [*] if specified, define a collection + variable value (comma-seperate to specify a list of values) + + e.g. + + update task jbpm$122 bpm:assignee=fred wf:notifyMe=false + +ok> end task [] + + End the task identified by . If is omitted, the + default transition is taken. + +## +## end +## diff --git a/config/alfresco/model/contentModel.xml b/config/alfresco/model/contentModel.xml index e6523fb8d9..ddbfc66083 100644 --- a/config/alfresco/model/contentModel.xml +++ b/config/alfresco/model/contentModel.xml @@ -1,711 +1,711 @@ - - - Alfresco Content Domain Model - Alfresco - 2005-09-29 - 1.0 - - - - - - - - - - - - - \<\?\/\:\|\xA3\xAC\%\&\+\;]+.*]]> - false - - - - - - - Object - sys:base - - - Name - d:text - true - - - - - - - cm:auditable - - - - - Folder - cm:cmobject - true - - - - false - true - - - sys:base - false - true - - false - - - - - - Content - cm:cmobject - true - - - d:content - false - - - true - false - true - - - - - - - Dictionary Model - cm:content - - - Model name - d:qname - true - - - Description - d:text - true - - - Author - d:text - true - - - Published Date - d:date - true - - - Version - d:text - true - - - Model Active - d:boolean - false - - - - - - Link Object - cm:cmobject - - - Link Destination - d:noderef - true - - - - - - Saved Query - cm:content - - - - System Folder - cm:folder - - - - Person - sys:base - - - d:text - true - - - d:noderef - true - - - d:text - true - - - d:text - true - - - d:text - - - d:text - - - d:text - - - d:text - - - d:text - - - - - - - - Category Root - cm:cmobject - - - - false - true - - - cm:category - false - true - - - - - sys:aspect_root - - - - - Category - cm:cmobject - - - - false - true - - - cm:category - false - true - - - - - - - - - - - - Titled - - - Title - d:text - - - Description - d:text - - - - - - Auditable - - - Created - d:datetime - true - true - - - Creator - d:text - true - true - - - Modified - d:datetime - true - true - - - Modifier - d:text - true - true - - - Accessed - d:datetime - true - - - - - - Localizable - - - Locale - - d:category - - - - - - Translatable - cm:localizable - - - - Translations - - cm:translationOf - false - false - - - cm:content - cm:hasTranslation - false - true - - - - - - - Transformable - - - Formats - - cm:formatOf - false - false - - - cm:content - cm:hasFormat - false - true - - - - - - - Templatable - - - Template - d:noderef - false - - - - - - Complianceable - cm:auditable - - - - Remove After - d:datetime - - - - - - Ownable - - - Owner - d:text - - - - - - Author - - - Author - d:text - - - - - - Dublin Core - cm:titled - - - Publisher - d:text - true - - - Contributor - d:text - true - - - Type - d:text - true - - - Identifier - d:text - true - - - Source - d:text - true - - - Coverage - d:text - true - - - Rights - d:text - true - - - Subject - d:text - true - - - - cm:auditable - cm:author - - - - - Basable - - - - cm:basedOn - false - true - - - cm:content - cm:hasBasis - false - true - - - - - - - Partable - - - - cm:partOf - false - true - - - cm:content - cm:hasPart - false - true - - - - - - - Referencing - - - - cm:referencedBy - false - true - - - cm:content - cm:references - false - true - - - - - - - Replacable - - - - cm:replacedBy - false - true - - - cm:content - cm:replaces - false - true - - - - - - - Effectivity - - - Effective From - d:datetime - - - Effective To - d:datetime - - - - - - Summarizable - - - Summary - d:text - - - - - - Countable - - - d:int - - - d:int - - - - - - Copied From - - - d:noderef - true - true - false - - true - false - true - - - - - - - Working Copy - - - d:text - true - true - - - - - - Versionable - - - Version Label - d:text - true - - - Initial Version - d:boolean - true - - - Auto Version - d:boolean - true - - - - - - Lockable - - - d:text - true - - - d:text - true - - - d:date - true - false - - - d:boolean - true - - - - - - - - - false - true - - - cm:person - false - true - - - - - - - Classifiable - - - - General Classifiable - cm:classifiable - - - Categories - d:category - false - true - - true - true - true - - - - - - - Attachable - - - - false - true - - - cm:cmobject - false - true - - - - - - - Emailed - - - Originator - d:text - - - Addressee - d:text - - - Addressees - d:text - true - - - Subject - d:text - - - Sent Date - d:datetime - - - - - - - References Node - - - Node Reference - d:noderef - true - - - - - - - + + + Alfresco Content Domain Model + Alfresco + 2005-09-29 + 1.0 + + + + + + + + + + + + + \<\?\/\:\|\xA3\xAC\%\&\+\;]+.*]]> + false + + + + + + + Object + sys:base + + + Name + d:text + true + + + + + + + cm:auditable + + + + + Folder + cm:cmobject + true + + + + false + true + + + sys:base + false + true + + false + + + + + + Content + cm:cmobject + true + + + d:content + false + + + true + false + true + + + + + + + Dictionary Model + cm:content + + + Model name + d:qname + true + + + Description + d:text + true + + + Author + d:text + true + + + Published Date + d:date + true + + + Version + d:text + true + + + Model Active + d:boolean + false + + + + + + Link Object + cm:cmobject + + + Link Destination + d:noderef + true + + + + + + Saved Query + cm:content + + + + System Folder + cm:folder + + + + Person + sys:base + + + d:text + true + + + d:noderef + true + + + d:text + true + + + d:text + true + + + d:text + + + d:text + + + d:text + + + d:text + + + d:text + + + + + + + + Category Root + cm:cmobject + + + + false + true + + + cm:category + false + true + + + + + sys:aspect_root + + + + + Category + cm:cmobject + + + + false + true + + + cm:category + false + true + + + + + + + + + + + + Titled + + + Title + d:text + + + Description + d:text + + + + + + Auditable + + + Created + d:datetime + true + true + + + Creator + d:text + true + true + + + Modified + d:datetime + true + true + + + Modifier + d:text + true + true + + + Accessed + d:datetime + true + + + + + + Localizable + + + Locale + + d:category + + + + + + Translatable + cm:localizable + + + + Translations + + cm:translationOf + false + false + + + cm:content + cm:hasTranslation + false + true + + + + + + + Transformable + + + Formats + + cm:formatOf + false + false + + + cm:content + cm:hasFormat + false + true + + + + + + + Templatable + + + Template + d:noderef + false + + + + + + Complianceable + cm:auditable + + + + Remove After + d:datetime + + + + + + Ownable + + + Owner + d:text + + + + + + Author + + + Author + d:text + + + + + + Dublin Core + cm:titled + + + Publisher + d:text + true + + + Contributor + d:text + true + + + Type + d:text + true + + + Identifier + d:text + true + + + Source + d:text + true + + + Coverage + d:text + true + + + Rights + d:text + true + + + Subject + d:text + true + + + + cm:auditable + cm:author + + + + + Basable + + + + cm:basedOn + false + true + + + cm:content + cm:hasBasis + false + true + + + + + + + Partable + + + + cm:partOf + false + true + + + cm:content + cm:hasPart + false + true + + + + + + + Referencing + + + + cm:referencedBy + false + true + + + cm:content + cm:references + false + true + + + + + + + Replacable + + + + cm:replacedBy + false + true + + + cm:content + cm:replaces + false + true + + + + + + + Effectivity + + + Effective From + d:datetime + + + Effective To + d:datetime + + + + + + Summarizable + + + Summary + d:text + + + + + + Countable + + + d:int + + + d:int + + + + + + Copied From + + + d:noderef + true + true + false + + true + false + true + + + + + + + Working Copy + + + d:text + true + true + + + + + + Versionable + + + Version Label + d:text + true + + + Initial Version + d:boolean + true + + + Auto Version + d:boolean + true + + + + + + Lockable + + + d:text + true + + + d:text + true + + + d:date + true + false + + + d:boolean + true + + + + + + + + + false + true + + + cm:person + false + true + + + + + + + Classifiable + + + + General Classifiable + cm:classifiable + + + Categories + d:category + false + true + + true + true + true + + + + + + + Attachable + + + + false + true + + + cm:cmobject + false + true + + + + + + + Emailed + + + Originator + d:text + + + Addressee + d:text + + + Addressees + d:text + true + + + Subject + d:text + + + Sent Date + d:datetime + + + + + + + References Node + + + Node Reference + d:noderef + true + + + + + + + diff --git a/config/alfresco/network-protocol-context.xml b/config/alfresco/network-protocol-context.xml index 10fa179cfe..f5d0408e00 100644 --- a/config/alfresco/network-protocol-context.xml +++ b/config/alfresco/network-protocol-context.xml @@ -57,6 +57,7 @@ + diff --git a/config/alfresco/public-services-context.xml b/config/alfresco/public-services-context.xml index 9138bd9d63..5cda07fcef 100644 --- a/config/alfresco/public-services-context.xml +++ b/config/alfresco/public-services-context.xml @@ -1,1186 +1,1194 @@ - - - - - - - - - - http://www.alfresco.org - - - - - - - - org.alfresco.service.ServiceRegistry - - - - - - - - - - - - - - org.alfresco.service.ServiceRegistry - - - Repository service registry - - - - - - - - - - - - org.alfresco.service.descriptor.DescriptorService - - - - - - - - - - - - - - - org.alfresco.service.descriptor.DescriptorService - - - Descriptor service - - - - - - - - - - - false - - - - - - - - - org.alfresco.service.namespace.NamespaceService - - - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.namespace.NamespaceService - - - Namespace service - - - - - - - - - - - - org.alfresco.service.cmr.dictionary.DictionaryService - - - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.dictionary.DictionaryService - - - Dictionary Service - - - - - - - - - org.alfresco.service.ServiceDescriptor - org.alfresco.service.cmr.repository.NodeService - - - - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.readOnly} - ${server.transaction.mode.readOnly} - ${server.transaction.mode.readOnly} - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.repository.NodeService - - - Node Service - - - - - - - - org.alfresco.service.cmr.repository.ContentService - - - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.repository.ContentService - - - Content Service - - - - - - - - org.alfresco.service.cmr.repository.MimetypeService - - - - - - - - - - - - - - - - - org.alfresco.service.cmr.repository.MimetypeService - - - Mime Type Service - - - - - - - - org.alfresco.service.cmr.search.SearchService - - - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.search.SearchService - - - Search Service - - - - - - - - org.alfresco.service.cmr.search.CategoryService - - - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.search.CategoryService - - - Category Service - - - - - - - - org.alfresco.service.cmr.repository.CopyService - - - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.repository.CopyService - - - Copy Service - - - - - - - - org.alfresco.service.cmr.lock.LockService - - - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.lock.LockService - - - Lock Service - - - - - - - - org.alfresco.service.cmr.version.VersionService - - - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.version.VersionService - - - Version Service - - - - - - - - org.alfresco.service.cmr.coci.CheckOutCheckInService - - - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.coci.CheckOutCheckInService - - - Version Service - - - - - - - - org.alfresco.service.cmr.rule.RuleService - - - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.rule.RuleService - - - Rule Service - - - - - - - - org.alfresco.service.cmr.view.ImporterService - - - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.view.ImporterService - - - Importer Service - - - - - - - - org.alfresco.service.cmr.view.ExporterService - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.view.ExporterService - - - Exporter Service - - - - - - - - org.alfresco.service.cmr.action.ActionService - - - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.action.ActionService - - - Action Service - - - - - - - - org.alfresco.service.cmr.security.PermissionService - - - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.security.PermissionService - - - Permission Service - - - - - - - - org.alfresco.service.cmr.security.AuthorityService - - - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.security.AuthorityService - - - Authority Service - - - - - - - - org.alfresco.service.cmr.security.OwnableService - - - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.security.OwnableService - - - OwnableService Service - - - - - - - - org.alfresco.service.cmr.security.PersonService - - - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.security.PersonService - - - PersonService Service - - - - - - - - org.alfresco.service.cmr.security.AuthenticationService - - - - - - - - - - - - - - - - - - - - - - PROPAGATION_NOT_SUPPORTED, readOnly - PROPAGATION_NOT_SUPPORTED, readOnly - PROPAGATION_NOT_SUPPORTED, readOnly - PROPAGATION_NOT_SUPPORTED, readOnly - PROPAGATION_NOT_SUPPORTED, readOnly - PROPAGATION_NOT_SUPPORTED, readOnly - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.security.AuthenticationService - - - AuthenticationService Service - - - - - - - - org.alfresco.service.cmr.repository.TemplateService - - - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.repository.TemplateService - - - TemplateService Service - - - - - - - - org.alfresco.service.cmr.repository.ScriptService - - - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.repository.ScriptService - - - ScriptService Service - - - - - - - - org.alfresco.service.cmr.model.FileFolderService - - - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.readOnly} - ${server.transaction.mode.readOnly} - ${server.transaction.mode.readOnly} - ${server.transaction.mode.readOnly} - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.model.FileFolderService - - - FileFolderService Service - - - - - - - - - - - - - - org.alfresco.service.cmr.avm.AVMService - - - - - - - - - - - - - - - org.alfresco.service.cmr.avm.AVMService - - - AVM Service - - - - - - - - - - ${server.transaction.mode.readOnly} - ${server.transaction.mode.default} - ${server.transaction.mode.default} - ${server.transaction.mode.default} - ${server.transaction.mode.readOnly} - ${server.transaction.mode.readOnly} - ${server.transaction.mode.default} - - - - - - - - - - - - - - - - - - org.alfresco.service.cmr.avmsync.AVMSyncService - - - AVM Tree Synchronization Service - - - - - - - - - - ${server.transaction.mode.readOnly} - ${server.transaction.mode.default} - ${server.transaction.mode.default} - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.avmsync.AVMSyncService - - - - - - - - - - - - - - - - - org.alfresco.service.cmr.workflow.WorkflowService - - - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.workflow.WorkflowService - - - Workflow Service - - - - - - - - - org.alfresco.service.cmr.audit.AuditService - - - - - - - - - - - - - - - - - - - - - - ${server.transaction.mode.default} - - - - - - - org.alfresco.service.cmr.audit.AuditService - - - Audit Service - - - - + + + + + + + + + + http://www.alfresco.org + + + + + + + + org.alfresco.service.ServiceRegistry + + + + + + + + + + + + + + org.alfresco.service.ServiceRegistry + + + Repository service registry + + + + + + + + + + + + org.alfresco.service.descriptor.DescriptorService + + + + + + + + + + + + + + + org.alfresco.service.descriptor.DescriptorService + + + Descriptor service + + + + + + + + + + + false + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.namespace.NamespaceService + + + Namespace service + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.dictionary.DictionaryService + + + Dictionary Service + + + + + + + + + org.alfresco.service.ServiceDescriptor + org.alfresco.service.cmr.repository.NodeService + + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.readOnly} + ${server.transaction.mode.readOnly} + ${server.transaction.mode.readOnly} + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.repository.NodeService + + + Node Service + + + + + + + + org.alfresco.service.cmr.repository.ContentService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.repository.ContentService + + + Content Service + + + + + + + + org.alfresco.service.cmr.repository.MimetypeService + + + + + + + + + + + + + + + + + org.alfresco.service.cmr.repository.MimetypeService + + + Mime Type Service + + + + + + + + org.alfresco.service.cmr.search.SearchService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.search.SearchService + + + Search Service + + + + + + + + org.alfresco.service.cmr.search.CategoryService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.search.CategoryService + + + Category Service + + + + + + + + org.alfresco.service.cmr.repository.CopyService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.repository.CopyService + + + Copy Service + + + + + + + + org.alfresco.service.cmr.lock.LockService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.lock.LockService + + + Lock Service + + + + + + + + org.alfresco.service.cmr.version.VersionService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.version.VersionService + + + Version Service + + + + + + + + org.alfresco.service.cmr.coci.CheckOutCheckInService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.coci.CheckOutCheckInService + + + Version Service + + + + + + + + org.alfresco.service.cmr.rule.RuleService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.rule.RuleService + + + Rule Service + + + + + + + + org.alfresco.service.cmr.view.ImporterService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.view.ImporterService + + + Importer Service + + + + + + + + org.alfresco.service.cmr.view.ExporterService + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.view.ExporterService + + + Exporter Service + + + + + + + + org.alfresco.service.cmr.action.ActionService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.action.ActionService + + + Action Service + + + + + + + + org.alfresco.service.cmr.security.PermissionService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.security.PermissionService + + + Permission Service + + + + + + + + org.alfresco.service.cmr.security.AuthorityService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.security.AuthorityService + + + Authority Service + + + + + + + + org.alfresco.service.cmr.security.OwnableService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.security.OwnableService + + + OwnableService Service + + + + + + + + org.alfresco.service.cmr.security.PersonService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.security.PersonService + + + PersonService Service + + + + + + + + org.alfresco.service.cmr.security.AuthenticationService + + + + + + + + + + + + + + + + + + + + + + PROPAGATION_NOT_SUPPORTED, readOnly + PROPAGATION_NOT_SUPPORTED, readOnly + PROPAGATION_NOT_SUPPORTED, readOnly + PROPAGATION_NOT_SUPPORTED, readOnly + PROPAGATION_NOT_SUPPORTED, readOnly + PROPAGATION_NOT_SUPPORTED, readOnly + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.security.AuthenticationService + + + AuthenticationService Service + + + + + + + + org.alfresco.service.cmr.repository.TemplateService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.repository.TemplateService + + + TemplateService Service + + + + + + + + org.alfresco.service.cmr.repository.ScriptService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.repository.ScriptService + + + ScriptService Service + + + + + + + + org.alfresco.service.cmr.model.FileFolderService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.readOnly} + ${server.transaction.mode.readOnly} + ${server.transaction.mode.readOnly} + ${server.transaction.mode.readOnly} + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.model.FileFolderService + + + FileFolderService Service + + + + + + + + + + + + + + org.alfresco.service.cmr.avm.AVMService + + + + + + + + + + + + + + + org.alfresco.service.cmr.avm.AVMService + + + AVM Service + + + + + + + + + + ${server.transaction.mode.readOnly} + ${server.transaction.mode.default} + ${server.transaction.mode.default} + ${server.transaction.mode.default} + ${server.transaction.mode.readOnly} + ${server.transaction.mode.readOnly} + ${server.transaction.mode.default} + + + + + + + + + + + + + + + + + + org.alfresco.service.cmr.avmsync.AVMSyncService + + + AVM Tree Synchronization Service + + + + + + + + + + ${server.transaction.mode.readOnly} + ${server.transaction.mode.default} + ${server.transaction.mode.default} + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.avmsync.AVMSyncService + + + + + + + + + + + + + + + + + org.alfresco.service.cmr.workflow.WorkflowService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.workflow.WorkflowService + + + Workflow Service + + + + + + + + + org.alfresco.service.cmr.audit.AuditService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.audit.AuditService + + + Audit Service + + + + diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml index fcf2b92d31..1ed94603a2 100644 --- a/config/alfresco/public-services-security-context.xml +++ b/config/alfresco/public-services-security-context.xml @@ -325,7 +325,7 @@ org.alfresco.service.cmr.repository.NodeService.getStores=AFTER_ACL_NODE.sys:base.Read org.alfresco.service.cmr.repository.NodeService.createStore=ACL_METHOD.ROLE_ADMINISTRATOR - org.alfresco.service.cmr.repository.NodeService.exists=ACL_NODE.0.sys:base.Read + org.alfresco.service.cmr.repository.NodeService.exists=ACL_ALLOW org.alfresco.service.cmr.repository.NodeService.getNodeStatus=ACL_NODE.0.sys:base.Read org.alfresco.service.cmr.repository.NodeService.getRootNode=ACL_NODE.0.sys:base.Read org.alfresco.service.cmr.repository.NodeService.createNode=ACL_NODE.0.sys:base.CreateChildren @@ -374,7 +374,7 @@ org.alfresco.service.cmr.model.FileFolderService.listFolders=ACL_NODE.0.sys:base.ReadChildren,AFTER_ACL_NODE.sys:base.Read org.alfresco.service.cmr.model.FileFolderService.search=ACL_NODE.0.sys:base.ReadChildren,AFTER_ACL_NODE.sys:base.Read org.alfresco.service.cmr.model.FileFolderService.searchSimple=ACL_NODE.0.sys:base.ReadChildren,AFTER_ACL_NODE.sys:base.Read - org.alfresco.service.cmr.model.FileFolderService.rename=ACL_PARENT.0.sys:base.CreateChildren,AFTER_ACL_NODE.sys:base.WriteProperties + org.alfresco.service.cmr.model.FileFolderService.rename=AFTER_ACL_NODE.sys:base.WriteProperties org.alfresco.service.cmr.model.FileFolderService.move=ACL_NODE.0.sys:base.DeleteNode,ACL_NODE.1.sys:base.CreateChildren org.alfresco.service.cmr.model.FileFolderService.copy=ACL_NODE.0.sys:base.Read,ACL_NODE.1.sys:base.CreateChildren org.alfresco.service.cmr.model.FileFolderService.create=ACL_NODE.0.sys:base.CreateChildren diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index bfa073e697..af47e22408 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -1,138 +1,137 @@ -# Directory configuration - -dir.root=./alf_data - -dir.contentstore=${dir.root}/contentstore -dir.contentstore.deleted=${dir.root}/contentstore.deleted - -dir.auditcontentstore=${dir.root}/audit.contentstore - -# The location for lucene index files -dir.indexes=${dir.root}/lucene-indexes - -# The location for lucene index locks -dir.indexes.lock=${dir.indexes}/locks - -# The index recovery mode (NONE, VALIDATE, AUTO, FULL) -index.recovery.mode=VALIDATE - -# #################### # -# Lucene configuration # -# #################### # -# -# Millisecond threshold for text transformations -# Slower transformers will force the text extraction to be asynchronous -# -lucene.maxAtomicTransformationTime=20 -# -# The maximum number of clauses that are allowed in a lucene query -# -lucene.query.maxClauses=10000 -# -# The size of the queue of nodes waiting for index -# Events are generated as nodes are changed, this is the maximum size of the queue used to coalesce event -# When this size is reached the lists of nodes will be indexed -# -lucene.indexer.batchSize=1000 -# -# Lucene index min merge docs - the in memory size of the index -# -lucene.indexer.minMergeDocs=1000 -# -# When lucene index files are merged together - it will try to keep this number of segments/files in -# -lucene.indexer.mergeFactor=10 -# -# Roughly the maximum number of nodes indexed in one file/segment -# -lucene.indexer.maxMergeDocs=100000 -# -# The number of terms from a document that will be indexed -# -lucene.indexer.maxFieldLength=10000 - -lucene.write.lock.timeout=10000 -lucene.commit.lock.timeout=100000 -lucene.lock.poll.interval=100 - -# Database configuration - -db.schema.update=true -db.driver=org.gjt.mm.mysql.Driver -db.name=alfresco -db.url=jdbc:mysql:///${db.name} -db.username=alfresco -db.password=alfresco -db.pool.initial=10 -db.pool.max=20 -db.pool.maxIdleTime=120 - -# Email configuration - -mail.host= -mail.port=25 -mail.username=anonymous -mail.password= -# Set this value to UTF-8 or similar for encoding of email messages as required -mail.encoding=UTF-8 -# Set this value to 7bit or similar for Asian encoding of email headers as required -mail.header= - -# System Configuration - -system.store=system://system -system.descriptor.childname=sys:descriptor -system.descriptor.current.childname=sys:descriptor-current - -# User config - -alfresco_user_store.store=user://alfrescoUserStore -alfresco_user_store.system_container.childname=sys:system -alfresco_user_store.user_container.childname=sys:people -alfresco_user_store.authorities_container.childname=sys:authorities - -# Spaces Archive Configuration -spaces.archive.store=archive://SpacesStore - -# Spaces Configuration - -spaces.store=workspace://SpacesStore -spaces.company_home.childname=app:company_home -spaces.guest_home.childname=app:guest_home -spaces.dictionary.childname=app:dictionary -spaces.templates.childname=app:space_templates -spaces.templates.content.childname=app:content_templates -spaces.templates.email.childname=app:email_templates -spaces.templates.rss.childname=app:rss_templates -spaces.savedsearches.childname=app:saved_searches -spaces.scripts.childname=app:scripts -spaces.wcm.childname=app:wcm -spaces.content_forms.childname=app:wcm_forms - -# Folders for storing people - -system.system_container.childname=sys:system -system.people_container.childname=sys:people - -# Folders for storing workflow related info - -system.workflow_container.childname=sys:workflow - -# Are user names case sensitive? -# ============================== -# -# NOTE: If you are using mysql you must have case sensitive collation -# -# You can do this when creating the alfresco database at the start -# CREATE DATABASE alfresco CHARACTER SET utf8 COLLATION utf8_bin; -# If you want to do this later this is a dump and load fix as it is done when the database, tables and columns are created. -# -# Must other databases are case sensitive by default. -# - -user.name.caseSensitive=false - -# AVM Specific properties. -avm.remote.idlestream.timeout=30000 -avm.remote.port=1313 - +# Directory configuration + +dir.root=./alf_data + +dir.contentstore=${dir.root}/contentstore +dir.contentstore.deleted=${dir.root}/contentstore.deleted + +dir.auditcontentstore=${dir.root}/audit.contentstore + +# The location for lucene index files +dir.indexes=${dir.root}/lucene-indexes + +# The location for lucene index locks +dir.indexes.lock=${dir.indexes}/locks + +# The index recovery mode (NONE, VALIDATE, AUTO, FULL) +index.recovery.mode=VALIDATE + +# #################### # +# Lucene configuration # +# #################### # +# +# Millisecond threshold for text transformations +# Slower transformers will force the text extraction to be asynchronous +# +lucene.maxAtomicTransformationTime=20 +# +# The maximum number of clauses that are allowed in a lucene query +# +lucene.query.maxClauses=10000 +# +# The size of the queue of nodes waiting for index +# Events are generated as nodes are changed, this is the maximum size of the queue used to coalesce event +# When this size is reached the lists of nodes will be indexed +# +lucene.indexer.batchSize=1000 +# +# Lucene index min merge docs - the in memory size of the index +# +lucene.indexer.minMergeDocs=1000 +# +# When lucene index files are merged together - it will try to keep this number of segments/files in +# +lucene.indexer.mergeFactor=10 +# +# Roughly the maximum number of nodes indexed in one file/segment +# +lucene.indexer.maxMergeDocs=100000 +# +# The number of terms from a document that will be indexed +# +lucene.indexer.maxFieldLength=10000 + +lucene.write.lock.timeout=10000 +lucene.commit.lock.timeout=100000 +lucene.lock.poll.interval=100 + +# Database configuration + +db.schema.update=true +db.driver=org.gjt.mm.mysql.Driver +db.name=alfresco +db.url=jdbc:mysql:///${db.name} +db.username=alfresco +db.password=alfresco +db.pool.initial=10 +db.pool.max=20 + +# Email configuration + +mail.host= +mail.port=25 +mail.username=anonymous +mail.password= +# Set this value to UTF-8 or similar for encoding of email messages as required +mail.encoding=UTF-8 +# Set this value to 7bit or similar for Asian encoding of email headers as required +mail.header= + +# System Configuration + +system.store=system://system +system.descriptor.childname=sys:descriptor +system.descriptor.current.childname=sys:descriptor-current + +# User config + +alfresco_user_store.store=user://alfrescoUserStore +alfresco_user_store.system_container.childname=sys:system +alfresco_user_store.user_container.childname=sys:people +alfresco_user_store.authorities_container.childname=sys:authorities + +# Spaces Archive Configuration +spaces.archive.store=archive://SpacesStore + +# Spaces Configuration + +spaces.store=workspace://SpacesStore +spaces.company_home.childname=app:company_home +spaces.guest_home.childname=app:guest_home +spaces.dictionary.childname=app:dictionary +spaces.templates.childname=app:space_templates +spaces.templates.content.childname=app:content_templates +spaces.templates.email.childname=app:email_templates +spaces.templates.rss.childname=app:rss_templates +spaces.savedsearches.childname=app:saved_searches +spaces.scripts.childname=app:scripts +spaces.wcm.childname=app:wcm +spaces.content_forms.childname=app:wcm_forms + +# Folders for storing people + +system.system_container.childname=sys:system +system.people_container.childname=sys:people + +# Folders for storing workflow related info + +system.workflow_container.childname=sys:workflow + +# Are user names case sensitive? +# ============================== +# +# NOTE: If you are using mysql you must have case sensitive collation +# +# You can do this when creating the alfresco database at the start +# CREATE DATABASE alfresco CHARACTER SET utf8 COLLATION utf8_bin; +# If you want to do this later this is a dump and load fix as it is done when the database, tables and columns are created. +# +# Must other databases are case sensitive by default. +# + +user.name.caseSensitive=false + +# AVM Specific properties. +avm.remote.idlestream.timeout=30000 +avm.remote.port=1313 + diff --git a/config/alfresco/rule-services-context.xml b/config/alfresco/rule-services-context.xml index 79955d57a9..d5697b927f 100644 --- a/config/alfresco/rule-services-context.xml +++ b/config/alfresco/rule-services-context.xml @@ -21,6 +21,9 @@ + + + false diff --git a/config/alfresco/script-services-context.xml b/config/alfresco/script-services-context.xml index fbbd6777a0..66e51cc9eb 100644 --- a/config/alfresco/script-services-context.xml +++ b/config/alfresco/script-services-context.xml @@ -2,9 +2,38 @@ + + + + + + + + + + + logger + + + + + + utils + + + + + + actions + + + + + + diff --git a/config/alfresco/templates/content/examples/show_audit.ftl b/config/alfresco/templates/content/examples/show_audit.ftl new file mode 100644 index 0000000000..d439d7c9ee --- /dev/null +++ b/config/alfresco/templates/content/examples/show_audit.ftl @@ -0,0 +1,173 @@ + <#-- Shows some general audit info about the current document --> + <#if document?exists> +

    Current Docuement Audit Info

    + Name: ${document.name}
    + + + + + + + + + + + + + + + + + + + <#list document.auditTrail as t> + + + + <#if t.auditService?exists> + + <#else> + + + <#if t.auditMethod?exists> + + <#else> + + + + <#if t.fail?exists> + + <#else> + + + <#if t.message?exists> + + <#else> + + + <#if t.methodArgumentsAsStrings[0]?exists> + + <#else> + + + <#if t.methodArgumentsAsStrings[1]?exists> + + <#else> + + + <#if t.methodArgumentsAsStrings[2]?exists> + + <#else> + + + <#if t.methodArgumentsAsStrings[3]?exists> + + <#else> + + + <#if t.methodArgumentsAsStrings[4]?exists> + + <#else> + + + <#if t.returnObjectAsString?exists> + + <#else> + + + <#if t.throwableAsString?exists> + + <#else> + + + + + +
    User NameApplicationServiceMethodTimestampFailedMessageArg 1Arg 2Arg 3Arg 4Arg 5ReturnThowableTX
    ${t.userIdentifier}${t.auditApplication}${t.auditService} ${t.auditMethod} ${t.date}${t.fail?string("FAILED", "OK")} ${t.message} ${t.methodArgumentsAsStrings[0]} ${t.methodArgumentsAsStrings[1]} ${t.methodArgumentsAsStrings[2]} ${t.methodArgumentsAsStrings[3]} ${t.methodArgumentsAsStrings[4]} ${t.returnObjectAsString} ${t.throwableAsString} ${t.txId}
    + <#elseif space?exists> +

    Current Space Audit Info:

    + Name: ${space.name}
    + + + + + + + + + + + + + + + + + + + + <#list space.auditTrail as t> + + + + <#if t.auditService?exists> + + <#else> + + + <#if t.auditMethod?exists> + + <#else> + + + + <#if t.fail?exists> + + <#else> + + + <#if t.message?exists> + + <#else> + + + <#if t.methodArgumentsAsStrings[0]?exists> + + <#else> + + + <#if t.methodArgumentsAsStrings[1]?exists> + + <#else> + + + <#if t.methodArgumentsAsStrings[2]?exists> + + <#else> + + + <#if t.methodArgumentsAsStrings[3]?exists> + + <#else> + + + <#if t.methodArgumentsAsStrings[4]?exists> + + <#else> + + + <#if t.returnObjectAsString?exists> + + <#else> + + + <#if t.throwableAsString?exists> + + <#else> + + + + + +
    User NameApplicationServiceMethodTimestampFailedMessageArg 1Arg 2Arg 3Arg 4Arg 5ReturnThowableTX
    ${t.userIdentifier}${t.auditApplication}${t.auditService} ${t.auditMethod} ${t.date}${t.fail?string("FAILED", "OK")} ${t.message} ${t.methodArgumentsAsStrings[0]} ${t.methodArgumentsAsStrings[1]} ${t.methodArgumentsAsStrings[2]} ${t.methodArgumentsAsStrings[3]} ${t.methodArgumentsAsStrings[4]} ${t.returnObjectAsString} ${t.throwableAsString} ${t.txId}
    + \ No newline at end of file diff --git a/config/alfresco/templates/content_template_examples.xml b/config/alfresco/templates/content_template_examples.xml index 85ac61c026..c5947af0cb 100644 --- a/config/alfresco/templates/content_template_examples.xml +++ b/config/alfresco/templates/content_template_examples.xml @@ -126,17 +126,17 @@ - + true - Displays the current state of records in a file plan space or a space containing a file plan. - contentUrl=classpath:alfresco/templates/content/examples/records_report.ftl|mimetype=text/plain|size=6134|encoding=UTF-8 - records_report.ftl - records_report.ftl + Displays the audit trail for an object. + contentUrl=classpath:alfresco/templates/content/examples/show_audit.ftl|mimetype=text/plain|size=6134|encoding=UTF-8 + show_audit.ftl + show_audit.ftl diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index 681bec09e2..bbaa2c9aa9 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -7,7 +7,7 @@ version.major=1 version.minor=4 version.revision=0 -version.label=RC1 +version.label= # Edition label diff --git a/config/alfresco/workflow-context.xml b/config/alfresco/workflow-context.xml index 468f5d350a..ae0f4a3ece 100644 --- a/config/alfresco/workflow-context.xml +++ b/config/alfresco/workflow-context.xml @@ -23,26 +23,33 @@ + + + + + + + + + + + alfresco.messages.workflow-interpreter-help + + + + - - true - - - - - - - - - - + true + + + @@ -73,7 +80,7 @@ - + diff --git a/source/cpp/CAlfrescoApp/source/alfresco/Alfresco.cpp b/source/cpp/CAlfrescoApp/source/alfresco/Alfresco.cpp index 31f08bcf3e..b35d4e6922 100644 --- a/source/cpp/CAlfrescoApp/source/alfresco/Alfresco.cpp +++ b/source/cpp/CAlfrescoApp/source/alfresco/Alfresco.cpp @@ -309,7 +309,7 @@ DesktopResponse AlfrescoInterface::runAction(AlfrescoActionInfo& action, Desktop // Build the run action I/O control request DataBuffer reqbuf( 1024); - DataBuffer respbuf( 256); + DataBuffer respbuf( 4096); reqbuf.putFixedString( IOSignature, IOSignatureLen); reqbuf.putString( action.getName()); diff --git a/source/java/org/alfresco/filesys/CIFSServer.java b/source/java/org/alfresco/filesys/CIFSServer.java index b62f92fff4..34fa3fe64a 100644 --- a/source/java/org/alfresco/filesys/CIFSServer.java +++ b/source/java/org/alfresco/filesys/CIFSServer.java @@ -26,12 +26,11 @@ import org.alfresco.filesys.netbios.server.NetBIOSNameServer; import org.alfresco.filesys.server.NetworkServer; import org.alfresco.filesys.server.config.ServerConfiguration; import org.alfresco.filesys.smb.server.SMBServer; +import org.alfresco.util.AbstractLifecycleBean; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.support.ClassPathXmlApplicationContext; /** @@ -41,7 +40,7 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; * * @author GKSpencer */ -public class CIFSServer implements ApplicationListener +public class CIFSServer extends AbstractLifecycleBean { private static final Log logger = LogFactory.getLog("org.alfresco.smb.server"); @@ -81,29 +80,6 @@ public class CIFSServer implements ApplicationListener return (filesysConfig != null && filesysConfig.isSMBServerEnabled()); } - /* - * (non-Javadoc) - * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) - */ - public void onApplicationEvent(ApplicationEvent event) - { - if (event instanceof ContextRefreshedEvent) - { - try - { - startServer(); - } - catch (SocketException e) - { - throw new AlfrescoRuntimeException("Failed to start CIFS server", e); - } - catch (IOException e) - { - throw new AlfrescoRuntimeException("Failed to start CIFS server", e); - } - } - } - /** * Start the CIFS server components * @@ -264,5 +240,27 @@ public class CIFSServer implements ApplicationListener System.exit(1); } + @Override + protected void onBootstrap(ApplicationEvent event) + { + try + { + startServer(); + } + catch (SocketException e) + { + throw new AlfrescoRuntimeException("Failed to start CIFS server", e); + } + catch (IOException e) + { + throw new AlfrescoRuntimeException("Failed to start CIFS server", e); + } + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + stopServer(); + } } diff --git a/source/java/org/alfresco/filesys/FTPServer.java b/source/java/org/alfresco/filesys/FTPServer.java index f77cb0b67d..270b209373 100644 --- a/source/java/org/alfresco/filesys/FTPServer.java +++ b/source/java/org/alfresco/filesys/FTPServer.java @@ -24,11 +24,11 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.filesys.ftp.FTPNetworkServer; import org.alfresco.filesys.server.NetworkServer; import org.alfresco.filesys.server.config.ServerConfiguration; +import org.alfresco.util.AbstractLifecycleBean; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -39,7 +39,7 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; * * @author GKSpencer */ -public class FTPServer implements ApplicationListener +public class FTPServer extends AbstractLifecycleBean { private static final Log logger = LogFactory.getLog("org.alfresco.ftp.server"); @@ -79,29 +79,6 @@ public class FTPServer implements ApplicationListener return (filesysConfig != null && filesysConfig.isFTPServerEnabled()); } - /* - * (non-Javadoc) - * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) - */ - public void onApplicationEvent(ApplicationEvent event) - { - if (event instanceof ContextRefreshedEvent) - { - try - { - startServer(); - } - catch (SocketException e) - { - throw new AlfrescoRuntimeException("Failed to start FTP server", e); - } - catch (IOException e) - { - throw new AlfrescoRuntimeException("Failed to start FTP server", e); - } - } - } - /** * Start the FTP server components * @@ -251,4 +228,28 @@ public class FTPServer implements ApplicationListener } System.exit(1); } + + @Override + protected void onBootstrap(ApplicationEvent event) + { + try + { + startServer(); + } + catch (SocketException e) + { + throw new AlfrescoRuntimeException("Failed to start FTP server", e); + } + catch (IOException e) + { + throw new AlfrescoRuntimeException("Failed to start FTP server", e); + } + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + stopServer(); + } + } diff --git a/source/java/org/alfresco/filesys/ftp/FTPSrvSession.java b/source/java/org/alfresco/filesys/ftp/FTPSrvSession.java index 16a9daa11c..afd55c32a8 100644 --- a/source/java/org/alfresco/filesys/ftp/FTPSrvSession.java +++ b/source/java/org/alfresco/filesys/ftp/FTPSrvSession.java @@ -852,7 +852,7 @@ public class FTPSrvSession extends SrvSession implements Runnable // DEBUG if ( logger.isDebugEnabled()) - logger.debug("Logon failed", ex); + logger.debug("Logon failed for user " + cInfo.getUserName()); } // Check if the logon was successful diff --git a/source/java/org/alfresco/filesys/server/auth/AlfrescoAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/AlfrescoAuthenticator.java index 817e7b361b..f26a8bfdd2 100644 --- a/source/java/org/alfresco/filesys/server/auth/AlfrescoAuthenticator.java +++ b/source/java/org/alfresco/filesys/server/auth/AlfrescoAuthenticator.java @@ -17,10 +17,17 @@ package org.alfresco.filesys.server.auth; import java.security.NoSuchAlgorithmException; +import net.sf.acegisecurity.Authentication; import org.alfresco.filesys.server.SrvSession; +import org.alfresco.filesys.server.auth.AuthContext; +import org.alfresco.filesys.server.auth.CifsAuthenticator; +import org.alfresco.filesys.server.auth.ClientInfo; +import org.alfresco.filesys.server.auth.NTLanManAuthContext; import org.alfresco.filesys.smb.server.SMBSrvSession; +import org.alfresco.filesys.util.HexDump; import org.alfresco.repo.security.authentication.NTLMMode; +import org.alfresco.repo.security.authentication.ntlm.NTLMPassthruToken; /** * Alfresco Authenticator Class @@ -88,10 +95,7 @@ public class AlfrescoAuthenticator extends CifsAuthenticator { // Use the existing authentication token - if ( client.isGuest()) - m_authComponent.setGuestUserAsCurrentUser(); - else - m_authComponent.setCurrentUser(mapUserNameToPerson(client.getUserName())); + m_authComponent.setCurrentUser(client.getUserName()); // Debug @@ -107,7 +111,7 @@ public class AlfrescoAuthenticator extends CifsAuthenticator int authSts = AUTH_DISALLOW; - if ( client.isGuest() || client.getUserName().equalsIgnoreCase(GUEST_USERNAME)) + if ( client.isGuest() || client.getUserName().equalsIgnoreCase(getGuestUserName())) { // Check if guest logons are allowed @@ -140,7 +144,13 @@ public class AlfrescoAuthenticator extends CifsAuthenticator authSts = doMD4UserAuthentication(client, sess, alg); } - + else + { + // Perform passthru authentication password check + + authSts = doPassthruUserAuthentication(client, sess, alg); + } + // Check if the logon status indicates a guest logon if ( authSts == AUTH_GUEST) @@ -172,6 +182,64 @@ public class AlfrescoAuthenticator extends CifsAuthenticator return authSts; } + /** + * Return an authentication context for the new session + * + * @return AuthContext + */ + public AuthContext getAuthContext( SMBSrvSession sess) + { + // Check if the client is already authenticated, and it is not a null logon + + AuthContext authCtx = null; + + if ( sess.hasAuthenticationContext() && sess.hasAuthenticationToken() && + sess.getClientInformation().getLogonType() != ClientInfo.LogonNull) + { + // Return the previous challenge, user is already authenticated + + authCtx = (NTLanManAuthContext) sess.getAuthenticationContext(); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Re-using existing challenge, already authenticated"); + } + else if ( m_authComponent.getNTLMMode() == NTLMMode.MD4_PROVIDER) + { + // Create a new authentication context for the session + + authCtx = new NTLanManAuthContext(); + sess.setAuthenticationContext( authCtx); + } + else + { + // Create an authentication token for the session + + NTLMPassthruToken authToken = new NTLMPassthruToken(); + + // Run the first stage of the passthru authentication to get the challenge + + m_authComponent.authenticate( authToken); + + // Save the authentication token for the second stage of the authentication + + sess.setAuthenticationToken(authToken); + + // Get the challenge from the token + + if ( authToken.getChallenge() != null) + { + authCtx = new NTLanManAuthContext( authToken.getChallenge().getBytes()); + sess.setAuthenticationContext( authCtx); + } + } + + // Return the authentication context + + return authCtx; + } + /** * Perform MD4 user authentication * @@ -217,6 +285,20 @@ public class AlfrescoAuthenticator extends CifsAuthenticator // Validate the password byte[] clientHash = client.getPassword(); + if ( clientHash == null || clientHash.length != 24) + { + // Use the secondary password hash from the client + + clientHash = client.getANSIPassword(); + + // DEBUG + + if ( logger.isDebugEnabled()) + { + logger.debug( "Using secondary password hash - " + HexDump.hexString(clientHash)); + logger.debug( " Local hash - " + HexDump.hexString( localHash)); + } + } if ( clientHash == null || clientHash.length != localHash.length) return CifsAuthenticator.AUTH_BADPASSWORD; @@ -229,7 +311,7 @@ public class AlfrescoAuthenticator extends CifsAuthenticator // Set the current user to be authenticated, save the authentication token - client.setAuthenticationToken( m_authComponent.setCurrentUser(mapUserNameToPerson(client.getUserName()))); + client.setAuthenticationToken( m_authComponent.setCurrentUser(client.getUserName())); // Get the users home folder node, if available @@ -259,4 +341,101 @@ public class AlfrescoAuthenticator extends CifsAuthenticator return allowGuest() ? CifsAuthenticator.AUTH_GUEST : CifsAuthenticator.AUTH_DISALLOW; } + + /** + * Perform passthru user authentication + * + * @param client Client information + * @param sess Server session + * @param alg Encryption algorithm + * @return int + */ + private final int doPassthruUserAuthentication(ClientInfo client, SrvSession sess, int alg) + { + // Get the authentication token for the session + + NTLMPassthruToken authToken = (NTLMPassthruToken) sess.getAuthenticationToken(); + + if ( authToken == null) + return CifsAuthenticator.AUTH_DISALLOW; + + // Get the appropriate hashed password for the algorithm + + int authSts = CifsAuthenticator.AUTH_DISALLOW; + byte[] hashedPassword = null; + + if ( alg == NTLM1) + hashedPassword = client.getPassword(); + else if ( alg == LANMAN) + hashedPassword = client.getANSIPassword(); + else + { + // Invalid/unsupported algorithm specified + + return CifsAuthenticator.AUTH_DISALLOW; + } + + // Set the username and hashed password in the authentication token + + authToken.setUserAndPassword( client.getUserName(), hashedPassword, alg); + + // Authenticate the user + + Authentication genAuthToken = null; + + try + { + // Run the second stage of the passthru authentication + + genAuthToken = m_authComponent.authenticate( authToken); + + // Check if the user has been logged on as a guest + + if (authToken.isGuestLogon()) + { + + // Check if the local server allows guest access + + if (allowGuest() == true) + { + + // Allow the user access as a guest + + authSts = CifsAuthenticator.AUTH_GUEST; + } + } + else + { + + // Allow the user full access to the server + + authSts = CifsAuthenticator.AUTH_ALLOW; + } + + // Set the current user to be authenticated, save the authentication token + + client.setAuthenticationToken( genAuthToken); + + // Get the users home folder node, if available + + getHomeFolderForUser( client); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Auth token " + genAuthToken); + } + catch ( Exception ex) + { + logger.error("Error during passthru authentication", ex); + } + + // Clear the authentication token + + sess.setAuthenticationToken(null); + + // Return the authentication status + + return authSts; + } } \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java index 7815a7cf8f..3effae1145 100644 --- a/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java +++ b/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java @@ -470,7 +470,7 @@ public abstract class CifsAuthenticator // Store the client maximum buffer size, maximum multiplexed requests count and client // capability flags - sess.setClientMaximumBufferSize(maxBufSize); + sess.setClientMaximumBufferSize(maxBufSize != 0 ? maxBufSize : SMBSrvSession.DefaultBufferSize); sess.setClientMaximumMultiplex(maxMpx); sess.setClientCapabilities(capabs); diff --git a/source/java/org/alfresco/filesys/server/auth/ClientInfo.java b/source/java/org/alfresco/filesys/server/auth/ClientInfo.java index 56eec14aeb..421c840583 100644 --- a/source/java/org/alfresco/filesys/server/auth/ClientInfo.java +++ b/source/java/org/alfresco/filesys/server/auth/ClientInfo.java @@ -71,6 +71,10 @@ public class ClientInfo private Authentication m_authToken; + // Authentication ticket, used for web access without having to re-authenticate + + private String m_authTicket; + // Home folder node private NodeRef m_homeNode; @@ -286,6 +290,26 @@ public class ClientInfo { return m_authToken; } + + /** + * Check if the client has an authentication ticket + * + * @return boolean + */ + public final boolean hasAuthenticationTicket() + { + return m_authTicket != null ? true : false; + } + + /** + * Return the authentication ticket + * + * @return String + */ + public final String getAuthenticationTicket() + { + return m_authTicket; + } /** * Check if the client has a home folder node @@ -409,6 +433,16 @@ public class ClientInfo { m_authToken = token; } + + /** + * Set the authentication ticket + * + * @param ticket String + */ + public final void setAuthenticationTicket(String ticket) + { + m_authTicket = ticket; + } /** * Set the home folder node @@ -448,6 +482,12 @@ public class ClientInfo str.append(",token="); str.append(getAuthenticationToken()); } + + if ( hasAuthenticationTicket()) + { + str.append(",ticket="); + str.append(getAuthenticationTicket()); + } if (isGuest()) str.append(",Guest"); diff --git a/source/java/org/alfresco/filesys/server/auth/EnterpriseCifsAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/EnterpriseCifsAuthenticator.java index c5647faff4..4f034ee1c7 100644 --- a/source/java/org/alfresco/filesys/server/auth/EnterpriseCifsAuthenticator.java +++ b/source/java/org/alfresco/filesys/server/auth/EnterpriseCifsAuthenticator.java @@ -584,7 +584,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca // Store the client maximum buffer size, maximum multiplexed requests count and client capability flags - sess.setClientMaximumBufferSize(maxBufSize); + sess.setClientMaximumBufferSize(maxBufSize != 0 ? maxBufSize : SMBSrvSession.DefaultBufferSize); sess.setClientMaximumMultiplex(maxMpx); sess.setClientCapabilities(capabs); diff --git a/source/java/org/alfresco/filesys/server/auth/ntlm/AlfrescoAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/ntlm/AlfrescoAuthenticator.java deleted file mode 100644 index 5b77d4171d..0000000000 --- a/source/java/org/alfresco/filesys/server/auth/ntlm/AlfrescoAuthenticator.java +++ /dev/null @@ -1,427 +0,0 @@ -/* - * Copyright (C) 2005-2006 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.filesys.server.auth.ntlm; - -import java.security.NoSuchAlgorithmException; -import net.sf.acegisecurity.Authentication; - -import org.alfresco.filesys.server.SrvSession; -import org.alfresco.filesys.server.auth.AuthContext; -import org.alfresco.filesys.server.auth.CifsAuthenticator; -import org.alfresco.filesys.server.auth.ClientInfo; -import org.alfresco.filesys.server.auth.NTLanManAuthContext; -import org.alfresco.filesys.smb.server.SMBSrvSession; -import org.alfresco.filesys.util.DataPacker; -import org.alfresco.repo.security.authentication.NTLMMode; -import org.alfresco.repo.security.authentication.ntlm.NTLMPassthruToken; - -/** - * Alfresco Authenticator Class - * - *

    The Alfresco authenticator implementation enables user level security mode using the Alfresco authentication - * component. - * - *

    Note: Switching off encrypted password support will cause later NT4 service pack releases and - * Win2000 to refuse to connect to the server without a registry update on the client. - * - * @author GKSpencer - */ -public class AlfrescoAuthenticator extends CifsAuthenticator -{ - /** - * Default Constructor - * - *

    Default to user mode security with encrypted password support. - */ - public AlfrescoAuthenticator() - { - } - - /** - * Validate that the authentication component supports the required mode - * - * @return boolean - */ - protected boolean validateAuthenticationMode() - { - // Make sure the authentication component supports MD4 hashed passwords or passthru mode - - if ( m_authComponent.getNTLMMode() != NTLMMode.MD4_PROVIDER && - m_authComponent.getNTLMMode() != NTLMMode.PASS_THROUGH) - return false; - return true; - } - - /** - * Authenticate a user - * - * @param client Client information - * @param sess Server session - * @param alg Encryption algorithm - */ - public int authenticateUser(ClientInfo client, SrvSession sess, int alg) - { - // Check if this is an SMB/CIFS null session logon. - // - // The null session will only be allowed to connect to the IPC$ named pipe share. - - if (client.isNullSession() && sess instanceof SMBSrvSession) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Null CIFS logon allowed"); - - return CifsAuthenticator.AUTH_ALLOW; - } - - // Check if the client is already authenticated, and it is not a null logon - - if ( client.getAuthenticationToken() != null && client.getLogonType() != ClientInfo.LogonNull) - { - // Use the existing authentication token - - m_authComponent.setCurrentUser(client.getUserName()); - - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Re-using existing authentication token"); - - // Return the authentication status - - return client.getLogonType() != ClientInfo.LogonGuest ? AUTH_ALLOW : AUTH_GUEST; - } - - // Check if this is a guest logon - - int authSts = AUTH_DISALLOW; - - if ( client.isGuest() || client.getUserName().equalsIgnoreCase(getGuestUserName())) - { - // Check if guest logons are allowed - - if ( allowGuest() == false) - return AUTH_DISALLOW; - - // Get a guest authentication token - - doGuestLogon( client, sess); - - // Indicate logged on as guest - - authSts = AUTH_GUEST; - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Authenticated user " + client.getUserName() + " sts=" + getStatusAsString(authSts)); - - // Return the guest status - - return authSts; - } - - // Check if MD4 or passthru mode is configured - - else if ( m_authComponent.getNTLMMode() == NTLMMode.MD4_PROVIDER) - { - // Perform local MD4 password check - - authSts = doMD4UserAuthentication(client, sess, alg); - } - else - { - // Perform passthru authentication password check - - authSts = doPassthruUserAuthentication(client, sess, alg); - } - - // Check if the logon status indicates a guest logon - - if ( authSts == AUTH_GUEST) - { - // Only allow the guest logon if user mapping is enabled - - if ( mapUnknownUserToGuest()) - { - // Logon as guest, setup the security context - - doGuestLogon( client, sess); - } - else - { - // Do not allow the guest logon - - authSts = AUTH_DISALLOW; - } - } - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Authenticated user " + client.getUserName() + " sts=" + getStatusAsString(authSts) + - " via " + (m_authComponent.getNTLMMode() == NTLMMode.MD4_PROVIDER ? "MD4" : "Passthru")); - - // Return the authentication status - - return authSts; - } - - /** - * Return an authentication context for the new session - * - * @return AuthContext - */ - public AuthContext getAuthContext( SMBSrvSession sess) - { - // Check if the client is already authenticated, and it is not a null logon - - AuthContext authCtx = null; - - if ( sess.hasAuthenticationContext() && sess.hasAuthenticationToken() && - sess.getClientInformation().getLogonType() != ClientInfo.LogonNull) - { - // Return the previous challenge, user is already authenticated - - authCtx = (NTLanManAuthContext) sess.getAuthenticationContext(); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Re-using existing challenge, already authenticated"); - } - else if ( m_authComponent.getNTLMMode() == NTLMMode.MD4_PROVIDER) - { - // Create a new authentication context for the session - - authCtx = new NTLanManAuthContext(); - sess.setAuthenticationContext( authCtx); - } - else - { - // Create an authentication token for the session - - NTLMPassthruToken authToken = new NTLMPassthruToken(); - - // Run the first stage of the passthru authentication to get the challenge - - m_authComponent.authenticate( authToken); - - // Save the authentication token for the second stage of the authentication - - sess.setAuthenticationToken(authToken); - - // Get the challenge from the token - - if ( authToken.getChallenge() != null) - { - authCtx = new NTLanManAuthContext( authToken.getChallenge().getBytes()); - sess.setAuthenticationContext( authCtx); - } - } - - // Return the authentication context - - return authCtx; - } - - /** - * Perform MD4 user authentication - * - * @param client Client information - * @param sess Server session - * @param alg Encryption algorithm - * @return int - */ - private final int doMD4UserAuthentication(ClientInfo client, SrvSession sess, int alg) - { - // Get the stored MD4 hashed password for the user, or null if the user does not exist - - String md4hash = m_authComponent.getMD4HashedPassword(client.getUserName()); - - if ( md4hash != null) - { - // Check if the client has supplied an NTLM hashed password, if not then do not allow access - - if ( client.getPassword() == null) - return CifsAuthenticator.AUTH_BADPASSWORD; - - try - { - // Generate the local encrypted password using the challenge that was sent to the client - - byte[] p21 = new byte[21]; - byte[] md4byts = m_md4Encoder.decodeHash(md4hash); - System.arraycopy(md4byts, 0, p21, 0, 16); - - // Get the challenge that was sent to the client - - NTLanManAuthContext authCtx = null; - - if ( sess.hasAuthenticationContext() && sess.getAuthenticationContext() instanceof NTLanManAuthContext) - authCtx = (NTLanManAuthContext) sess.getAuthenticationContext(); - else - return CifsAuthenticator.AUTH_DISALLOW; - - // Generate the local hash of the password using the same challenge - - byte[] localHash = getEncryptor().doNTLM1Encryption(p21, authCtx.getChallenge()); - - // Validate the password - - byte[] clientHash = client.getPassword(); - - if ( clientHash == null || clientHash.length != localHash.length) - return CifsAuthenticator.AUTH_BADPASSWORD; - - for ( int i = 0; i < clientHash.length; i++) - { - if ( clientHash[i] != localHash[i]) - return CifsAuthenticator.AUTH_BADPASSWORD; - } - - // Set the current user to be authenticated, save the authentication token - - client.setAuthenticationToken( m_authComponent.setCurrentUser(client.getUserName())); - - // Get the users home folder node, if available - - getHomeFolderForUser( client); - - // Passwords match, grant access - - return CifsAuthenticator.AUTH_ALLOW; - } - catch (NoSuchAlgorithmException ex) - { - } - - // Error during password check, do not allow access - - return CifsAuthenticator.AUTH_DISALLOW; - } - - // Check if this is an SMB/CIFS null session logon. - // - // The null session will only be allowed to connect to the IPC$ named pipe share. - - if (client.isNullSession() && sess instanceof SMBSrvSession) - return CifsAuthenticator.AUTH_ALLOW; - - // User does not exist, check if guest access is allowed - - return allowGuest() ? CifsAuthenticator.AUTH_GUEST : CifsAuthenticator.AUTH_DISALLOW; - } - - /** - * Perform passthru user authentication - * - * @param client Client information - * @param sess Server session - * @param alg Encryption algorithm - * @return int - */ - private final int doPassthruUserAuthentication(ClientInfo client, SrvSession sess, int alg) - { - // Get the authentication token for the session - - NTLMPassthruToken authToken = (NTLMPassthruToken) sess.getAuthenticationToken(); - - if ( authToken == null) - return CifsAuthenticator.AUTH_DISALLOW; - - // Get the appropriate hashed password for the algorithm - - int authSts = CifsAuthenticator.AUTH_DISALLOW; - byte[] hashedPassword = null; - - if ( alg == NTLM1) - hashedPassword = client.getPassword(); - else if ( alg == LANMAN) - hashedPassword = client.getANSIPassword(); - else - { - // Invalid/unsupported algorithm specified - - return CifsAuthenticator.AUTH_DISALLOW; - } - - // Set the username and hashed password in the authentication token - - authToken.setUserAndPassword( client.getUserName(), hashedPassword, alg); - - // Authenticate the user - - Authentication genAuthToken = null; - - try - { - // Run the second stage of the passthru authentication - - genAuthToken = m_authComponent.authenticate( authToken); - - // Check if the user has been logged on as a guest - - if (authToken.isGuestLogon()) - { - - // Check if the local server allows guest access - - if (allowGuest() == true) - { - - // Allow the user access as a guest - - authSts = CifsAuthenticator.AUTH_GUEST; - } - } - else - { - - // Allow the user full access to the server - - authSts = CifsAuthenticator.AUTH_ALLOW; - } - - // Set the current user to be authenticated, save the authentication token - - client.setAuthenticationToken( genAuthToken); - - // Get the users home folder node, if available - - getHomeFolderForUser( client); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Auth token " + genAuthToken); - } - catch ( Exception ex) - { - logger.error("Error during passthru authentication", ex); - } - - // Clear the authentication token - - sess.setAuthenticationToken(null); - - // Return the authentication status - - return authSts; - } -} \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/server/auth/ntlm/NTLMLogonDetails.java b/source/java/org/alfresco/filesys/server/auth/ntlm/NTLMLogonDetails.java index c5c8f7f314..4615408bd5 100644 --- a/source/java/org/alfresco/filesys/server/auth/ntlm/NTLMLogonDetails.java +++ b/source/java/org/alfresco/filesys/server/auth/ntlm/NTLMLogonDetails.java @@ -39,6 +39,10 @@ public class NTLMLogonDetails private String m_authSrvAddr; + // Date/time authentication was started + + private long m_createTime; + // Date/time the user was authenticated private long m_authTime; @@ -61,6 +65,7 @@ public class NTLMLogonDetails */ public NTLMLogonDetails() { + m_createTime = System.currentTimeMillis(); } /** @@ -74,6 +79,8 @@ public class NTLMLogonDetails */ public NTLMLogonDetails(String user, String wks, String domain, boolean guest, String authSrv) { + m_createTime = System.currentTimeMillis(); + setDetails(user, wks, domain, guest, authSrv); } @@ -117,6 +124,16 @@ public class NTLMLogonDetails return m_authSrvAddr; } + /** + * Return the date/time the authentication was started + * + * @return long + */ + public final long createdAt() + { + return m_createTime; + } + /** * Return the date/time the user was authenticated * diff --git a/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java b/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java index 46c7e16839..a48040b950 100644 --- a/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java +++ b/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java @@ -81,11 +81,10 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.security.AuthenticationService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.AbstractLifecycleBean; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.ContextRefreshedEvent; /** *

    @@ -93,7 +92,7 @@ import org.springframework.context.event.ContextRefreshedEvent; * * @author Gary K. Spencer */ -public class ServerConfiguration implements ApplicationListener +public class ServerConfiguration extends AbstractLifecycleBean { // Debug logging @@ -425,18 +424,6 @@ public class ServerConfiguration implements ApplicationListener return initialised; } - /* - * (non-Javadoc) - * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) - */ - public void onApplicationEvent(ApplicationEvent event) - { - if (event instanceof ContextRefreshedEvent) - { - init(); - } - } - /** * Initialize the configuration using the configuration service */ @@ -1791,9 +1778,7 @@ public class ServerConfiguration implements ApplicationListener // Load the Alfresco authenticator dynamically - auth = loadAuthenticatorClass("org.alfresco.filesys.server.auth.ntlm.AlfrescoAuthenticator"); - if ( auth == null) - auth = loadAuthenticatorClass("org.alfresco.filesys.server.auth.AlfrescoAuthenticator"); + auth = loadAuthenticatorClass("org.alfresco.filesys.server.auth.AlfrescoAuthenticator"); if ( auth == null) throw new AlfrescoRuntimeException("Failed to load Alfresco authenticator"); @@ -3359,4 +3344,17 @@ public class ServerConfiguration implements ApplicationListener return srvAuth; } + + @Override + protected void onBootstrap(ApplicationEvent event) + { + init(); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + // NO-OP + } + } \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/smb/server/repo/ContentDiskDriver.java b/source/java/org/alfresco/filesys/smb/server/repo/ContentDiskDriver.java index 049e5d4e7d..7ce6b8705f 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/ContentDiskDriver.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/ContentDiskDriver.java @@ -18,6 +18,7 @@ package org.alfresco.filesys.smb.server.repo; import java.io.FileNotFoundException; import java.io.IOException; +import java.net.InetAddress; import java.util.List; import javax.transaction.UserTransaction; @@ -58,7 +59,6 @@ import org.alfresco.filesys.util.WildCard; import org.alfresco.model.ContentModel; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.coci.CheckOutCheckInService; import org.alfresco.service.cmr.lock.NodeLockedException; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.NodeRef; @@ -66,6 +66,7 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.AuthenticationService; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.transaction.TransactionService; @@ -91,6 +92,10 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface private static final String KEY_ROOT_PATH = "rootPath"; private static final String KEY_RELATIVE_PATH = "relativePath"; + // Token name to substitute current servers DNS name or TCP/IP address into the webapp URL + + private static final String TokenLocalName = "${localname}"; + // Services and helpers private CifsHelper cifsHelper; @@ -102,6 +107,7 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface private PermissionService permissionService; private AuthenticationComponent authComponent; + private AuthenticationService authService; // Service registry for desktop actions @@ -127,6 +133,16 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface return this.cifsHelper; } + /** + * Return the authentication service + * + * @return AuthenticationService + */ + public final AuthenticationService getAuthenticationService() + { + return authService; + } + /** * Return the transaction service * @@ -185,7 +201,7 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface { return this.serviceRegistry; } - + /** * @param contentService the content service */ @@ -255,6 +271,16 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface { this.authComponent = authComponent; } + + /** + * Set the authentication service + * + * @param authService AuthenticationService + */ + public void setAuthenticationService(AuthenticationService authService) + { + this.authService = authService; + } /** * Parse and validate the parameter string and create a device context object for this instance @@ -418,7 +444,39 @@ public class ContentDiskDriver implements DiskInterface, IOCtlInterface if ( pseudoName.getValue().endsWith(".url") == false) throw new DeviceContextException("URL link file must end with .url, " + pseudoName.getValue()); - // Set the URL link file name and web path + // Check if the URL path name contains the local name token + + int pos = path.indexOf(TokenLocalName); + if (pos != -1) + { + + // Get the local server name + + String srvName = "localhost"; + + try + { + srvName = InetAddress.getLocalHost().getHostName(); + } + catch ( Exception ex) + { + } + + // Rebuild the host name substituting the token with the local server name + + StringBuilder hostStr = new StringBuilder(); + + hostStr.append( path.substring(0, pos)); + hostStr.append(srvName); + + pos += TokenLocalName.length(); + if (pos < path.length()) + hostStr.append( path.substring(pos)); + + path = hostStr.toString(); + } + + // Set the URL link file name and web path context.setURLFileName( pseudoName.getValue()); context.setURLPrefix( path); diff --git a/source/java/org/alfresco/filesys/smb/server/repo/ContentIOControlHandler.java b/source/java/org/alfresco/filesys/smb/server/repo/ContentIOControlHandler.java index d9cb60e028..f83cad068a 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/ContentIOControlHandler.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/ContentIOControlHandler.java @@ -19,6 +19,7 @@ package org.alfresco.filesys.smb.server.repo; import java.io.FileNotFoundException; import org.alfresco.filesys.server.SrvSession; +import org.alfresco.filesys.server.auth.ClientInfo; import org.alfresco.filesys.server.filesys.IOControlNotImplementedException; import org.alfresco.filesys.server.filesys.NetworkFile; import org.alfresco.filesys.server.filesys.TreeConnection; @@ -30,10 +31,12 @@ import org.alfresco.filesys.smb.server.repo.ContentDiskDriver; import org.alfresco.filesys.smb.server.repo.IOControlHandler; import org.alfresco.filesys.util.DataBuffer; import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.service.cmr.lock.LockType; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.AuthenticationService; import org.alfresco.service.transaction.TransactionService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -85,6 +88,16 @@ public class ContentIOControlHandler implements IOControlHandler return contentDriver.getCifsHelper(); } + /** + * Return the authentication service + * + * @return AuthenticationService + */ + public final AuthenticationService getAuthenticationService() + { + return contentDriver.getAuthenticationService(); + } + /** * Return the transaction service * @@ -511,6 +524,11 @@ public class ContentIOControlHandler implements IOControlHandler // Start a transaction sess.beginTransaction( getTransactionService(), true); + + // Get an authentication ticket for the client, or validate the existing ticket. The ticket can be used when + // generating URLs for the client-side application so that the user does not have to re-authenticate + + getTicketForClient( sess); // Get the list of targets for the action @@ -624,4 +642,65 @@ public class ContentIOControlHandler implements IOControlHandler return respBuf; } + + /** + * Get, or validate, an authentication ticket for the client + * + * @param sess SrvSession + */ + private final void getTicketForClient(SrvSession sess) + { + // Get the client information and check if there is a ticket allocated + + ClientInfo cInfo = sess.getClientInformation(); + if ( cInfo == null) + return; + + boolean needTicket = true; + + if ( cInfo.hasAuthenticationTicket()) + { + // Validate the existing ticket, it may have expired + + try + { + // Validate the existing ticket + + getAuthenticationService().validate( cInfo.getAuthenticationTicket()); + needTicket = false; + } + catch ( AuthenticationException ex) + { + // Invalidate the current ticket + + try + { + getAuthenticationService().invalidateTicket( cInfo.getAuthenticationTicket()); + cInfo.setAuthenticationTicket( null); + } + catch (Exception ex2) + { + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Error during invalidate ticket", ex2); + } + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Auth ticket expired or invalid"); + } + } + + // Check if a ticket needs to be allocated + + if ( needTicket == true) + { + // Allocate a new ticket and store in the client information for this session + + String ticket = getAuthenticationService().getCurrentTicket(); + cInfo.setAuthenticationTicket( ticket); + } + } } diff --git a/source/java/org/alfresco/filesys/smb/server/repo/DesktopAction.java b/source/java/org/alfresco/filesys/smb/server/repo/DesktopAction.java index 1bcc759564..e5ca0a28e7 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/DesktopAction.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/DesktopAction.java @@ -19,10 +19,12 @@ package org.alfresco.filesys.smb.server.repo; import java.io.File; import java.io.UnsupportedEncodingException; +import java.net.InetAddress; import java.net.URL; import java.net.URLDecoder; import org.alfresco.config.ConfigElement; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.filesys.server.filesys.DiskSharedDevice; import org.alfresco.filesys.smb.server.repo.pseudo.LocalPseudoFile; import org.alfresco.filesys.smb.server.repo.pseudo.PseudoFile; @@ -88,7 +90,11 @@ public abstract class DesktopAction { public static final int StsLaunchURL = 7; public static final int StsCommandLine = 8; - // Action name + // Token name to substitute current servers DNS name or TCP/IP address into the webapp URL + + private static final String TokenLocalName = "${localname}"; + + // Action name private String m_name; @@ -109,6 +115,10 @@ public abstract class DesktopAction { private ContentDiskDriver m_contentDriver; private ContentContext m_contentContext; + // Webapp URL + + private String m_webappURL; + // Debug enable flag private boolean m_debug; @@ -254,6 +264,26 @@ public abstract class DesktopAction { return m_debug; } + /** + * Check if the webapp URL is set + * + * @return boolean + */ + public final boolean hasWebappURL() + { + return m_webappURL != null ? true : false; + } + + /** + * Return the webapp URL + * + * @return String + */ + public final String getWebappURL() + { + return m_webappURL; + } + /** * Initialize the desktop action * @@ -348,7 +378,50 @@ public abstract class DesktopAction { if ( findConfigElement("noConfirm", global, config) != null && hasPreProcessAction(PreConfirmAction)) setPreProcessActions(getPreProcessActions() - PreConfirmAction); + + // Check if the webapp URL has been specified + + ConfigElement webURL = findConfigElement("webpath", global, config); + if ( webURL != null) + { + // Check if the path name contains the local name token + + String webPath = webURL.getValue(); + if ( webPath.endsWith("/") == false) + webPath = webPath + "/"; + int pos = webPath.indexOf(TokenLocalName); + if (pos != -1) + { + + // Get the local server name + + String srvName = "localhost"; + + try + { + srvName = InetAddress.getLocalHost().getHostName(); + } + catch ( Exception ex) + { + } + + // Rebuild the host name substituting the token with the local server name + + StringBuilder hostStr = new StringBuilder(); + + hostStr.append(webPath.substring(0, pos)); + hostStr.append(srvName); + + pos += TokenLocalName.length(); + if (pos < webPath.length()) + hostStr.append(webPath.substring(pos)); + + webPath = hostStr.toString(); + setWebappURL( webPath); + } + } + // Check if debug output is enabled for the action ConfigElement debug = findConfigElement("debug", global, config); @@ -511,6 +584,16 @@ public abstract class DesktopAction { m_debug = ena; } + /** + * Set the webapp URL + * + * @param urlStr String + */ + public final void setWebappURL(String urlStr) + { + m_webappURL = urlStr; + } + /** * Equality check * diff --git a/source/java/org/alfresco/filesys/smb/server/repo/DesktopParams.java b/source/java/org/alfresco/filesys/smb/server/repo/DesktopParams.java index 1a2e294135..6a45926dc2 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/DesktopParams.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/DesktopParams.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.List; import org.alfresco.filesys.server.SrvSession; +import org.alfresco.filesys.server.auth.ClientInfo; import org.alfresco.filesys.server.filesys.NetworkFile; import org.alfresco.service.cmr.repository.NodeRef; @@ -88,6 +89,19 @@ public class DesktopParams { { return m_session; } + + /** + * Return the authentication ticket for the user/session + * + * @return String + */ + public final String getTicket() + { + ClientInfo cInfo = m_session.getClientInformation(); + if ( cInfo != null) + return cInfo.getAuthenticationTicket(); + return null; + } /** * Return the working directory node diff --git a/source/java/org/alfresco/filesys/smb/server/repo/desk/JavaScriptDesktopAction.java b/source/java/org/alfresco/filesys/smb/server/repo/desk/JavaScriptDesktopAction.java index 303ff36272..1a361e62f2 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/desk/JavaScriptDesktopAction.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/desk/JavaScriptDesktopAction.java @@ -284,6 +284,11 @@ public class JavaScriptDesktopAction extends DesktopAction { model.put("deskParams", params); model.put("out", System.out); + // Add the webapp URL, if valid + + if ( hasWebappURL()) + model.put("webURL", getWebappURL()); + // Start a transaction params.getSession().beginTransaction(getTransactionService(), false); diff --git a/source/java/org/alfresco/filesys/smb/server/repo/desk/URLDesktopAction.java b/source/java/org/alfresco/filesys/smb/server/repo/desk/URLDesktopAction.java index ca63252963..2a91523ae7 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/desk/URLDesktopAction.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/desk/URLDesktopAction.java @@ -16,6 +16,9 @@ */ package org.alfresco.filesys.smb.server.repo.desk; +import java.net.InetAddress; +import java.net.UnknownHostException; + import org.alfresco.filesys.smb.server.repo.DesktopAction; import org.alfresco.filesys.smb.server.repo.DesktopParams; import org.alfresco.filesys.smb.server.repo.DesktopResponse; @@ -45,8 +48,29 @@ public class URLDesktopAction extends DesktopAction { @Override public DesktopResponse runAction(DesktopParams params) { - // Return a URL in the status message + // Get the local IP address - return new DesktopResponse(StsLaunchURL, "http://www.alfresco.com"); + String ipAddr = null; + + try + { + ipAddr = InetAddress.getLocalHost().getHostAddress(); + } + catch (UnknownHostException ex) + { + } + + // Return a URL in the status message to browse to the folder node + + StringBuilder urlStr = new StringBuilder(); + + urlStr.append( "http://"); + urlStr.append(ipAddr); + urlStr.append(":8080/alfresco/navigate/browse/workspace/SpacesStore/"); + urlStr.append( params.getFolderNode().getId()); + urlStr.append("?ticket="); + urlStr.append(params.getTicket()); + + return new DesktopResponse(StsLaunchURL, urlStr.toString()); } } diff --git a/source/java/org/alfresco/filesys/smb/server/repo/pseudo/MemoryNetworkFile.java b/source/java/org/alfresco/filesys/smb/server/repo/pseudo/MemoryNetworkFile.java index 76d7cafe63..8eb403b31f 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/pseudo/MemoryNetworkFile.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/pseudo/MemoryNetworkFile.java @@ -19,7 +19,6 @@ package org.alfresco.filesys.smb.server.repo.pseudo; import java.io.IOException; -import org.alfresco.filesys.server.filesys.AccessDeniedException; import org.alfresco.filesys.server.filesys.FileInfo; import org.alfresco.filesys.server.filesys.NetworkFile; import org.alfresco.filesys.smb.SeekType; @@ -222,9 +221,7 @@ public class MemoryNetworkFile extends NetworkFile */ public void truncateFile(long siz) throws IOException { - // Do not allow the file to be written to - - throw new AccessDeniedException("Cannot truncate pseudo file"); + // Allow the truncate, do not alter the pseduo file data } /** @@ -236,9 +233,7 @@ public class MemoryNetworkFile extends NetworkFile */ public void writeFile(byte[] buf, int len, int pos) throws java.io.IOException { - // Do not allow the file to be written to - - throw new AccessDeniedException("Cannot write to pseudo file"); + // Allow the write, just do not do anything } /** @@ -252,8 +247,6 @@ public class MemoryNetworkFile extends NetworkFile */ public void writeFile(byte[] buf, int len, int pos, long offset) throws java.io.IOException { - // Do not allow the file to be written to - - throw new AccessDeniedException("Cannot write to pseudo file"); + // Allow the write, just do not do anything } } diff --git a/source/java/org/alfresco/filesys/smb/server/repo/pseudo/PseudoNetworkFile.java b/source/java/org/alfresco/filesys/smb/server/repo/pseudo/PseudoNetworkFile.java index 0ae631897c..4bcd2cefe0 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/pseudo/PseudoNetworkFile.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/pseudo/PseudoNetworkFile.java @@ -21,7 +21,6 @@ import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; -import org.alfresco.filesys.server.filesys.AccessDeniedException; import org.alfresco.filesys.server.filesys.NetworkFile; import org.alfresco.filesys.smb.SeekType; @@ -272,9 +271,7 @@ public class PseudoNetworkFile extends NetworkFile */ public void truncateFile(long siz) throws IOException { - // Do not allow the file to be written to - - throw new AccessDeniedException("Cannot truncate pseudo file"); + // Allow the truncate, just do not do anything } /** @@ -286,9 +283,7 @@ public class PseudoNetworkFile extends NetworkFile */ public void writeFile(byte[] buf, int len, int pos) throws java.io.IOException { - // Do not allow the file to be written to - - throw new AccessDeniedException("Cannot write to pseudo file"); + // Allow the write, just do not do anything } /** @@ -302,8 +297,6 @@ public class PseudoNetworkFile extends NetworkFile */ public void writeFile(byte[] buf, int len, int pos, long offset) throws java.io.IOException { - // Do not allow the file to be written to - - throw new AccessDeniedException("Cannot write to pseudo file"); + // Allow the write, just do not do anything } } diff --git a/source/java/org/alfresco/jcr/exporter/JCRSystemXMLExporter.java b/source/java/org/alfresco/jcr/exporter/JCRSystemXMLExporter.java index a9d3b3e9bf..8f973ed183 100644 --- a/source/java/org/alfresco/jcr/exporter/JCRSystemXMLExporter.java +++ b/source/java/org/alfresco/jcr/exporter/JCRSystemXMLExporter.java @@ -178,7 +178,7 @@ public class JCRSystemXMLExporter implements Exporter Value[] mixinValues = mixinTypes.getValues(); for (int i = 0; i < mixinValues.length; i++) { - value(nodeRef, JCRMixinTypesProperty.PROPERTY_NAME, mixinValues[i], i); + value(nodeRef, JCRMixinTypesProperty.PROPERTY_NAME, mixinValues[i].getString(), i); } endProperty(nodeRef, JCRMixinTypesProperty.PROPERTY_NAME); diff --git a/source/java/org/alfresco/model/ContentModel.java b/source/java/org/alfresco/model/ContentModel.java index 7873190322..e74aaf3652 100644 --- a/source/java/org/alfresco/model/ContentModel.java +++ b/source/java/org/alfresco/model/ContentModel.java @@ -1,268 +1,268 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.model; - -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; - - -/** - * Content Model Constants - */ -public interface ContentModel -{ - // - // System Model Definitions - // - - // base type constants - static final QName TYPE_BASE = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "base"); - static final QName ASPECT_REFERENCEABLE = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "referenceable"); - static final QName PROP_STORE_PROTOCOL = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "store-protocol"); - static final QName PROP_STORE_IDENTIFIER = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "store-identifier"); - static final QName PROP_NODE_UUID = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "node-uuid"); - static final QName PROP_NODE_DBID = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "node-dbid"); - - // tag for incomplete nodes - static final QName ASPECT_INCOMPLETE = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "incomplete"); - - // tag for temporary nodes - static final QName ASPECT_TEMPORARY = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "temporary"); - - // archived nodes aspect constants - static final QName ASPECT_ARCHIVED = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archived"); - static final QName PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedOriginalParentAssoc"); - static final QName PROP_ARCHIVED_BY = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedBy"); - static final QName PROP_ARCHIVED_DATE = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedDate"); - static final QName PROP_ARCHIVED_ORIGINAL_OWNER = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedOriginalOwner"); - static final QName ASPECT_ARCHIVED_ASSOCS = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archived-assocs"); - static final QName PROP_ARCHIVED_PARENT_ASSOCS = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedParentAssocs"); - static final QName PROP_ARCHIVED_CHILD_ASSOCS = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedChildAssocs"); - static final QName PROP_ARCHIVED_SOURCE_ASSOCS = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedSourceAssocs"); - static final QName PROP_ARCHIVED_TARGET_ASSOCS = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedTargetAssocs"); - - // referenceable aspect constants - static final QName TYPE_REFERENCE = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "reference"); - static final QName PROP_REFERENCE = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "reference"); - - // container type constants - static final QName TYPE_CONTAINER = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "container"); - /** child association type supported by {@link #TYPE_CONTAINER} */ - static final QName ASSOC_CHILDREN =QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "children"); - - // roots - static final QName ASPECT_ROOT = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "aspect_root"); - static final QName TYPE_STOREROOT = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "store_root"); - - // descriptor properties - static final QName PROP_SYS_VERSION_MAJOR = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "versionMajor"); - static final QName PROP_SYS_VERSION_MINOR = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "versionMinor"); - static final QName PROP_SYS_VERSION_REVISION = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "versionRevision"); - static final QName PROP_SYS_VERSION_LABEL = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "versionLabel"); - static final QName PROP_SYS_VERSION_BUILD = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "versionBuild"); - static final QName PROP_SYS_VERSION_SCHEMA = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "versionSchema"); - static final QName PROP_SYS_VERSION_EDITION = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "versionEdition"); - - - // - // Content Model Definitions - // - - // content management type constants - static final QName TYPE_CMOBJECT = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "cmobject"); - static final QName PROP_NAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "name"); - - // copy aspect constants - static final QName ASPECT_COPIEDFROM = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "copiedfrom"); - static final QName PROP_COPY_REFERENCE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "source"); - - // working copy aspect contants - static final QName ASPECT_WORKING_COPY = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "workingcopy"); - static final QName PROP_WORKING_COPY_OWNER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "workingCopyOwner"); - - // content type and aspect constants - static final QName TYPE_CONTENT = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "content"); - static final QName PROP_CONTENT = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "content"); - - // title aspect - static final QName ASPECT_TITLED = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "titled"); - static final QName PROP_TITLE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "title"); - static final QName PROP_DESCRIPTION = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "description"); - - // auditable aspect - static final QName ASPECT_AUDITABLE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "auditable"); - static final QName PROP_CREATED = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "created"); - static final QName PROP_CREATOR = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "creator"); - static final QName PROP_MODIFIED = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "modified"); - static final QName PROP_MODIFIER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "modifier"); - static final QName PROP_ACCESSED = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "accessed"); - - // author aspect - static final QName ASPECT_AUTHOR = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "author"); - static final QName PROP_AUTHOR = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "author"); - - // categories - static final QName TYPE_CATEGORYROOT = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "category_root"); - static final QName ASPECT_CLASSIFIABLE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "classifiable"); - //static final QName ASPECT_CATEGORISATION = QName.createQName(NamespaceService.ALFRESCO_URI, "aspect_categorisation"); - static final QName ASPECT_GEN_CLASSIFIABLE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "generalclassifiable"); - static final QName TYPE_CATEGORY = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "category"); - static final QName PROP_CATEGORIES = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "categories"); - static final QName ASSOC_CATEGORIES = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "categories"); - static final QName ASSOC_SUBCATEGORIES = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "subcategories"); - - // lock aspect - public final static QName ASPECT_LOCKABLE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "lockable"); - public final static QName PROP_LOCK_OWNER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "lockOwner"); - public final static QName PROP_LOCK_TYPE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "lockType"); - public final static QName PROP_EXPIRY_DATE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "expiryDate"); - - // version aspect - static final QName ASPECT_VERSIONABLE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "versionable"); - static final QName PROP_VERSION_LABEL = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "versionLabel"); - static final QName PROP_INITIAL_VERSION = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "initialVersion"); - static final QName PROP_AUTO_VERSION = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "autoVersion"); - - // folders - static final QName TYPE_SYSTEM_FOLDER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "systemfolder"); - static final QName TYPE_FOLDER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "folder"); - /** child association type supported by {@link #TYPE_FOLDER} */ - static final QName ASSOC_CONTAINS = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "contains"); - - // person - static final QName TYPE_PERSON = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "person"); - static final QName PROP_USERNAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "userName"); - static final QName PROP_HOMEFOLDER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "homeFolder"); - static final QName PROP_FIRSTNAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "firstName"); - static final QName PROP_LASTNAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "lastName"); - static final QName PROP_EMAIL = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "email"); - static final QName PROP_ORGID = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "organizationId"); - static final QName PROP_HOME_FOLDER_PROVIDER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "homeFolderProvider"); - static final QName PROP_DEFAULT_HOME_FOLDER_PATH = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "defaultHomeFolderPath"); - - - - // Ownable aspect - static final QName ASPECT_OWNABLE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "ownable"); - static final QName PROP_OWNER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "owner"); - - // Templatable aspect - static final QName ASPECT_TEMPLATABLE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "templatable"); - static final QName PROP_TEMPLATE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "template"); - - // Dictionary model - public static final QName TYPE_DICTIONARY_MODEL = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "dictionaryModel"); - public static final QName PROP_MODEL_NAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "modelName"); - public static final QName PROP_MODEL_DESCRIPTION = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "modelDescription"); - public static final QName PROP_MODEL_AUTHOR = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "modelAuthor"); - public static final QName PROP_MODEL_PUBLISHED_DATE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "modelPublishedDate"); - public static final QName PROP_MODEL_VERSION = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "modelVersion"); - public static final QName PROP_MODEL_ACTIVE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "modelActive"); - - // referencing aspect - public static final QName ASPECT_REFERENCING = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "referencing"); - public static final QName ASSOC_REFERENCES = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "references"); - - // link object - public static final QName TYPE_LINK = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "link"); - public static final QName PROP_LINK_DESTINATION = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "destination"); - - // email aspect - public static final QName ASPECT_MAILED = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "emailed"); - public static final QName PROP_SENTDATE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "sentdate"); - public static final QName PROP_ORIGINATOR = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "originator"); - public static final QName PROP_ADDRESSEE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "addressee"); - public static final QName PROP_ADDRESSEES = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "addressees"); - public static final QName PROP_SUBJECT = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "subjectline"); - - // countable aspect - public static final QName ASPECT_COUNTABLE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "countable"); - public static final QName PROP_HITS = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "hits"); - public static final QName PROP_COUNTER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "counter"); - - // References Node Aspect. - public static final QName ASPECT_REFERENCES_NODE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "referencesnode"); - public static final QName PROP_NODE_REF = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "noderef"); - - // - // Application Model Definitions - // - - // workflow - static final QName ASPECT_SIMPLE_WORKFLOW = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "simpleworkflow"); - static final QName PROP_APPROVE_STEP = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "approveStep"); - static final QName PROP_APPROVE_FOLDER = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "approveFolder"); - static final QName PROP_APPROVE_MOVE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "approveMove"); - static final QName PROP_REJECT_STEP = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "rejectStep"); - static final QName PROP_REJECT_FOLDER = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "rejectFolder"); - static final QName PROP_REJECT_MOVE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "rejectMove"); - - // ui facets aspect - static final QName ASPECT_UIFACETS = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "uifacets"); - static final QName PROP_ICON = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "icon"); - - // inlineeditable aspect - static final QName ASPECT_INLINEEDITABLE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "inlineeditable"); - static final QName PROP_EDITINLINE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "editInline"); - - // configurable aspect - static final QName ASPECT_CONFIGURABLE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "configurable"); - static final QName TYPE_CONFIGURATIONS = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "configurations"); - static final QName ASSOC_CONFIGURATIONS = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "configurations"); - - // object links - static final QName TYPE_FILELINK = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "filelink"); - static final QName TYPE_FOLDERLINK = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "folderlink"); - - // feed source aspect - static final QName ASPECT_FEEDSOURCE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "feedsource"); - static final QName PROP_FEEDTEMPLATE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "template"); - - // AVM web folder - static final QName TYPE_AVMWEBFOLDER = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "webfolder"); - static final QName PROP_AVMSTORE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "avmstore"); - static final QName ASSOC_WEBUSER = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "webuser"); - static final QName TYPE_WEBUSER = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "webuser"); - static final QName PROP_WEBUSERNAME = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "username"); - static final QName PROP_WEBUSERROLE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "role"); - - - // - // User Model Definitions - // - - static final String USER_MODEL_URI = "http://www.alfresco.org/model/user/1.0"; - static final String USER_MODEL_PREFIX = "usr"; - - static final QName TYPE_USER = QName.createQName(USER_MODEL_URI, "user"); - static final QName PROP_USER_USERNAME = QName.createQName(USER_MODEL_URI, "username"); - static final QName PROP_PASSWORD = QName.createQName(USER_MODEL_URI, "password"); - static final QName PROP_ENABLED = QName.createQName(USER_MODEL_URI, "enabled"); - static final QName PROP_ACCOUNT_EXPIRES = QName.createQName(USER_MODEL_URI, "accountExpires"); - static final QName PROP_ACCOUNT_EXPIRY_DATE = QName.createQName(USER_MODEL_URI, "accountExpiryDate"); - static final QName PROP_CREDENTIALS_EXPIRE = QName.createQName(USER_MODEL_URI, "credentialsExpire"); - static final QName PROP_CREDENTIALS_EXPIRY_DATE = QName.createQName(USER_MODEL_URI, "credentialsExpiryDate"); - static final QName PROP_ACCOUNT_LOCKED = QName.createQName(USER_MODEL_URI, "accountLocked"); - static final QName PROP_SALT = QName.createQName(USER_MODEL_URI, "salt"); - - static final QName TYPE_AUTHORITY = QName.createQName(USER_MODEL_URI, "authority"); - - static final QName TYPE_AUTHORITY_CONTAINER = QName.createQName(USER_MODEL_URI, "authorityContainer"); - static final QName PROP_AUTHORITY_NAME = QName.createQName(USER_MODEL_URI, "authorityName"); - static final QName ASSOC_MEMBER = QName.createQName(USER_MODEL_URI, "member"); - static final QName PROP_MEMBERS = QName.createQName(USER_MODEL_URI, "members"); -} +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.model; + +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + + +/** + * Content Model Constants + */ +public interface ContentModel +{ + // + // System Model Definitions + // + + // base type constants + static final QName TYPE_BASE = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "base"); + static final QName ASPECT_REFERENCEABLE = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "referenceable"); + static final QName PROP_STORE_PROTOCOL = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "store-protocol"); + static final QName PROP_STORE_IDENTIFIER = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "store-identifier"); + static final QName PROP_NODE_UUID = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "node-uuid"); + static final QName PROP_NODE_DBID = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "node-dbid"); + + // tag for incomplete nodes + static final QName ASPECT_INCOMPLETE = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "incomplete"); + + // tag for temporary nodes + static final QName ASPECT_TEMPORARY = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "temporary"); + + // archived nodes aspect constants + static final QName ASPECT_ARCHIVED = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archived"); + static final QName PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedOriginalParentAssoc"); + static final QName PROP_ARCHIVED_BY = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedBy"); + static final QName PROP_ARCHIVED_DATE = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedDate"); + static final QName PROP_ARCHIVED_ORIGINAL_OWNER = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedOriginalOwner"); + static final QName ASPECT_ARCHIVED_ASSOCS = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archived-assocs"); + static final QName PROP_ARCHIVED_PARENT_ASSOCS = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedParentAssocs"); + static final QName PROP_ARCHIVED_CHILD_ASSOCS = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedChildAssocs"); + static final QName PROP_ARCHIVED_SOURCE_ASSOCS = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedSourceAssocs"); + static final QName PROP_ARCHIVED_TARGET_ASSOCS = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedTargetAssocs"); + + // referenceable aspect constants + static final QName TYPE_REFERENCE = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "reference"); + static final QName PROP_REFERENCE = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "reference"); + + // container type constants + static final QName TYPE_CONTAINER = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "container"); + /** child association type supported by {@link #TYPE_CONTAINER} */ + static final QName ASSOC_CHILDREN =QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "children"); + + // roots + static final QName ASPECT_ROOT = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "aspect_root"); + static final QName TYPE_STOREROOT = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "store_root"); + + // descriptor properties + static final QName PROP_SYS_VERSION_MAJOR = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "versionMajor"); + static final QName PROP_SYS_VERSION_MINOR = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "versionMinor"); + static final QName PROP_SYS_VERSION_REVISION = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "versionRevision"); + static final QName PROP_SYS_VERSION_LABEL = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "versionLabel"); + static final QName PROP_SYS_VERSION_BUILD = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "versionBuild"); + static final QName PROP_SYS_VERSION_SCHEMA = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "versionSchema"); + static final QName PROP_SYS_VERSION_EDITION = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "versionEdition"); + + + // + // Content Model Definitions + // + + // content management type constants + static final QName TYPE_CMOBJECT = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "cmobject"); + static final QName PROP_NAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "name"); + + // copy aspect constants + static final QName ASPECT_COPIEDFROM = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "copiedfrom"); + static final QName PROP_COPY_REFERENCE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "source"); + + // working copy aspect contants + static final QName ASPECT_WORKING_COPY = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "workingcopy"); + static final QName PROP_WORKING_COPY_OWNER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "workingCopyOwner"); + + // content type and aspect constants + static final QName TYPE_CONTENT = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "content"); + static final QName PROP_CONTENT = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "content"); + + // title aspect + static final QName ASPECT_TITLED = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "titled"); + static final QName PROP_TITLE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "title"); + static final QName PROP_DESCRIPTION = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "description"); + + // auditable aspect + static final QName ASPECT_AUDITABLE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "auditable"); + static final QName PROP_CREATED = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "created"); + static final QName PROP_CREATOR = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "creator"); + static final QName PROP_MODIFIED = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "modified"); + static final QName PROP_MODIFIER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "modifier"); + static final QName PROP_ACCESSED = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "accessed"); + + // author aspect + static final QName ASPECT_AUTHOR = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "author"); + static final QName PROP_AUTHOR = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "author"); + + // categories + static final QName TYPE_CATEGORYROOT = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "category_root"); + static final QName ASPECT_CLASSIFIABLE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "classifiable"); + //static final QName ASPECT_CATEGORISATION = QName.createQName(NamespaceService.ALFRESCO_URI, "aspect_categorisation"); + static final QName ASPECT_GEN_CLASSIFIABLE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "generalclassifiable"); + static final QName TYPE_CATEGORY = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "category"); + static final QName PROP_CATEGORIES = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "categories"); + static final QName ASSOC_CATEGORIES = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "categories"); + static final QName ASSOC_SUBCATEGORIES = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "subcategories"); + + // lock aspect + public final static QName ASPECT_LOCKABLE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "lockable"); + public final static QName PROP_LOCK_OWNER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "lockOwner"); + public final static QName PROP_LOCK_TYPE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "lockType"); + public final static QName PROP_EXPIRY_DATE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "expiryDate"); + + // version aspect + static final QName ASPECT_VERSIONABLE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "versionable"); + static final QName PROP_VERSION_LABEL = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "versionLabel"); + static final QName PROP_INITIAL_VERSION = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "initialVersion"); + static final QName PROP_AUTO_VERSION = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "autoVersion"); + + // folders + static final QName TYPE_SYSTEM_FOLDER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "systemfolder"); + static final QName TYPE_FOLDER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "folder"); + /** child association type supported by {@link #TYPE_FOLDER} */ + static final QName ASSOC_CONTAINS = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "contains"); + + // person + static final QName TYPE_PERSON = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "person"); + static final QName PROP_USERNAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "userName"); + static final QName PROP_HOMEFOLDER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "homeFolder"); + static final QName PROP_FIRSTNAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "firstName"); + static final QName PROP_LASTNAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "lastName"); + static final QName PROP_EMAIL = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "email"); + static final QName PROP_ORGID = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "organizationId"); + static final QName PROP_HOME_FOLDER_PROVIDER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "homeFolderProvider"); + static final QName PROP_DEFAULT_HOME_FOLDER_PATH = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "defaultHomeFolderPath"); + + + + // Ownable aspect + static final QName ASPECT_OWNABLE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "ownable"); + static final QName PROP_OWNER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "owner"); + + // Templatable aspect + static final QName ASPECT_TEMPLATABLE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "templatable"); + static final QName PROP_TEMPLATE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "template"); + + // Dictionary model + public static final QName TYPE_DICTIONARY_MODEL = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "dictionaryModel"); + public static final QName PROP_MODEL_NAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "modelName"); + public static final QName PROP_MODEL_DESCRIPTION = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "modelDescription"); + public static final QName PROP_MODEL_AUTHOR = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "modelAuthor"); + public static final QName PROP_MODEL_PUBLISHED_DATE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "modelPublishedDate"); + public static final QName PROP_MODEL_VERSION = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "modelVersion"); + public static final QName PROP_MODEL_ACTIVE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "modelActive"); + + // referencing aspect + public static final QName ASPECT_REFERENCING = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "referencing"); + public static final QName ASSOC_REFERENCES = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "references"); + + // link object + public static final QName TYPE_LINK = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "link"); + public static final QName PROP_LINK_DESTINATION = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "destination"); + + // email aspect + public static final QName ASPECT_MAILED = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "emailed"); + public static final QName PROP_SENTDATE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "sentdate"); + public static final QName PROP_ORIGINATOR = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "originator"); + public static final QName PROP_ADDRESSEE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "addressee"); + public static final QName PROP_ADDRESSEES = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "addressees"); + public static final QName PROP_SUBJECT = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "subjectline"); + + // countable aspect + public static final QName ASPECT_COUNTABLE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "countable"); + public static final QName PROP_HITS = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "hits"); + public static final QName PROP_COUNTER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "counter"); + + // References Node Aspect. + public static final QName ASPECT_REFERENCES_NODE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "referencesnode"); + public static final QName PROP_NODE_REF = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "noderef"); + + // + // Application Model Definitions + // + + // workflow + static final QName ASPECT_SIMPLE_WORKFLOW = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "simpleworkflow"); + static final QName PROP_APPROVE_STEP = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "approveStep"); + static final QName PROP_APPROVE_FOLDER = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "approveFolder"); + static final QName PROP_APPROVE_MOVE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "approveMove"); + static final QName PROP_REJECT_STEP = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "rejectStep"); + static final QName PROP_REJECT_FOLDER = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "rejectFolder"); + static final QName PROP_REJECT_MOVE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "rejectMove"); + + // ui facets aspect + static final QName ASPECT_UIFACETS = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "uifacets"); + static final QName PROP_ICON = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "icon"); + + // inlineeditable aspect + static final QName ASPECT_INLINEEDITABLE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "inlineeditable"); + static final QName PROP_EDITINLINE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "editInline"); + + // configurable aspect + static final QName ASPECT_CONFIGURABLE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "configurable"); + static final QName TYPE_CONFIGURATIONS = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "configurations"); + static final QName ASSOC_CONFIGURATIONS = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "configurations"); + + // object links + static final QName TYPE_FILELINK = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "filelink"); + static final QName TYPE_FOLDERLINK = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "folderlink"); + + // feed source aspect + static final QName ASPECT_FEEDSOURCE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "feedsource"); + static final QName PROP_FEEDTEMPLATE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "template"); + + // AVM web folder + static final QName TYPE_AVMWEBFOLDER = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "webfolder"); + static final QName PROP_AVMSTORE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "avmstore"); + static final QName ASSOC_WEBUSER = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "webuser"); + static final QName TYPE_WEBUSER = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "webuser"); + static final QName PROP_WEBUSERNAME = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "username"); + static final QName PROP_WEBUSERROLE = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "role"); + + + // + // User Model Definitions + // + + static final String USER_MODEL_URI = "http://www.alfresco.org/model/user/1.0"; + static final String USER_MODEL_PREFIX = "usr"; + + static final QName TYPE_USER = QName.createQName(USER_MODEL_URI, "user"); + static final QName PROP_USER_USERNAME = QName.createQName(USER_MODEL_URI, "username"); + static final QName PROP_PASSWORD = QName.createQName(USER_MODEL_URI, "password"); + static final QName PROP_ENABLED = QName.createQName(USER_MODEL_URI, "enabled"); + static final QName PROP_ACCOUNT_EXPIRES = QName.createQName(USER_MODEL_URI, "accountExpires"); + static final QName PROP_ACCOUNT_EXPIRY_DATE = QName.createQName(USER_MODEL_URI, "accountExpiryDate"); + static final QName PROP_CREDENTIALS_EXPIRE = QName.createQName(USER_MODEL_URI, "credentialsExpire"); + static final QName PROP_CREDENTIALS_EXPIRY_DATE = QName.createQName(USER_MODEL_URI, "credentialsExpiryDate"); + static final QName PROP_ACCOUNT_LOCKED = QName.createQName(USER_MODEL_URI, "accountLocked"); + static final QName PROP_SALT = QName.createQName(USER_MODEL_URI, "salt"); + + static final QName TYPE_AUTHORITY = QName.createQName(USER_MODEL_URI, "authority"); + + static final QName TYPE_AUTHORITY_CONTAINER = QName.createQName(USER_MODEL_URI, "authorityContainer"); + static final QName PROP_AUTHORITY_NAME = QName.createQName(USER_MODEL_URI, "authorityName"); + static final QName ASSOC_MEMBER = QName.createQName(USER_MODEL_URI, "member"); + static final QName PROP_MEMBERS = QName.createQName(USER_MODEL_URI, "members"); +} diff --git a/source/java/org/alfresco/repo/action/executer/CopyActionExecuter.java b/source/java/org/alfresco/repo/action/executer/CopyActionExecuter.java index 1c1d92ebc4..229a844a9e 100644 --- a/source/java/org/alfresco/repo/action/executer/CopyActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/CopyActionExecuter.java @@ -162,7 +162,7 @@ public class CopyActionExecuter extends ActionExecuterAbstractBase else { // Create a new copy of the node - this.copyService.copy( + this.copyService.copyAndRename( actionedUponNodeRef, destinationParent, destinationAssocTypeQName, diff --git a/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java b/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java index 4375b6e101..b921a3e0f7 100644 --- a/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.List; import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.node.integrity.IntegrityChecker; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.transaction.TransactionUtil; import org.alfresco.repo.transaction.TransactionUtil.TransactionWork; @@ -329,6 +330,9 @@ public abstract class AbstractPatch implements Patch { public String doWork() throws Exception { + // downgrade integrity checking + IntegrityChecker.setWarnInTransaction(); + String report = applyInternal(); // done return report; @@ -389,7 +393,8 @@ public abstract class AbstractPatch implements Patch /** * This method does the work. All transactions and thread-safety will be taken care of by this class. - * Any exception will result in the transaction being rolled back. + * Any exception will result in the transaction being rolled back. Integrity checks are downgraded + * for the duration of the transaction. * * @return Returns the report (only success messages). * @see #apply() diff --git a/source/java/org/alfresco/repo/admin/patch/PatchExecuter.java b/source/java/org/alfresco/repo/admin/patch/PatchExecuter.java index d0f2655d69..dbb5e0b592 100644 --- a/source/java/org/alfresco/repo/admin/patch/PatchExecuter.java +++ b/source/java/org/alfresco/repo/admin/patch/PatchExecuter.java @@ -21,11 +21,10 @@ import java.util.List; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.i18n.I18NUtil; +import org.alfresco.util.AbstractLifecycleBean; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.ContextRefreshedEvent; /** * This component is responsible for ensuring that patches are applied @@ -33,7 +32,7 @@ import org.springframework.context.event.ContextRefreshedEvent; * * @author Derek Hulley */ -public class PatchExecuter implements ApplicationListener +public class PatchExecuter extends AbstractLifecycleBean { private static final String MSG_CHECKING = "patch.executer.checking"; private static final String MSG_NO_PATCHES_REQUIRED = "patch.executer.no_patches_required"; @@ -101,16 +100,16 @@ public class PatchExecuter implements ApplicationListener } } - /* - * (non-Javadoc) - * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) - */ - public void onApplicationEvent(ApplicationEvent event) + @Override + protected void onBootstrap(ApplicationEvent event) { - if (event instanceof ContextRefreshedEvent) - { - applyOutstandingPatches(); - } + applyOutstandingPatches(); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + // NOOP } } diff --git a/source/java/org/alfresco/repo/admin/patch/impl/AbstractPermissionChangePatch.java b/source/java/org/alfresco/repo/admin/patch/impl/AbstractPermissionChangePatch.java new file mode 100644 index 0000000000..7f0cc516de --- /dev/null +++ b/source/java/org/alfresco/repo/admin/patch/impl/AbstractPermissionChangePatch.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.admin.patch.impl; + +import java.util.List; + +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.repo.domain.DbAccessControlEntry; +import org.alfresco.repo.domain.DbPermission; +import org.alfresco.repo.domain.hibernate.DbPermissionImpl; +import org.alfresco.service.namespace.QName; +import org.hibernate.Query; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.springframework.orm.hibernate3.HibernateCallback; +import org.springframework.orm.hibernate3.support.HibernateDaoSupport; + +/** + * Provides common functionality to change a permission type and/or name. + * + * @author Derek Hulley + */ +public abstract class AbstractPermissionChangePatch extends AbstractPatch +{ + private HibernateHelper helper; + + public AbstractPermissionChangePatch() + { + helper = new HibernateHelper(); + } + + public void setSessionFactory(SessionFactory sessionFactory) + { + this.helper.setSessionFactory(sessionFactory); + } + + /** + * Helper method to rename (move) a permission. This involves checking for the existence of the + * new permission and then moving all the entries to point to the new permission. + * + * @param oldTypeQName the old permission type + * @param oldName the old permission name + * @param newTypeQName the new permission type + * @param newName the new permission name + * @return Returns the number of permission entries modified + */ + protected int renamePermission(QName oldTypeQName, String oldName, QName newTypeQName, String newName) + { + return helper.createAndUpdatePermission(oldTypeQName, oldName, newTypeQName, newName); + } + + /** Helper to get a permission entity */ + private static class GetPermissionCallback implements HibernateCallback + { + private QName typeQName; + private String name; + public GetPermissionCallback(QName typeQName, String name) + { + this.typeQName = typeQName; + this.name = name; + } + public Object doInHibernate(Session session) + { + // flush any outstanding entities + session.flush(); + + Query query = session.getNamedQuery(HibernateHelper.QUERY_GET_PERMISSION); + query.setParameter("permissionTypeQName", typeQName) + .setString("permissionName", name); + return query.uniqueResult(); + } + } + + private static class HibernateHelper extends HibernateDaoSupport + { + private static final String QUERY_GET_PERMISSION = "permission.GetPermission"; + private static final String QUERY_GET_ENTRIES_TO_CHANGE = "permission.patch.GetAccessControlEntriesToChangePermissionOn"; + + public int createAndUpdatePermission( + final QName oldTypeQName, + final String oldName, + final QName newTypeQName, + final String newName) + { + if (oldTypeQName.equals(newTypeQName) && oldName.equals(newName)) + { + throw new IllegalArgumentException("Cannot move permission to itself: " + oldTypeQName + "-" + oldName); + } + + HibernateCallback getNewPermissionCallback = new GetPermissionCallback(newTypeQName, newName); + DbPermission permission = (DbPermission) getHibernateTemplate().execute(getNewPermissionCallback); + if (permission == null) + { + // create the permission + permission = new DbPermissionImpl(); + permission.setTypeQname(newTypeQName); + permission.setName(newName); + // save + getHibernateTemplate().save(permission); + } + final DbPermission newPermission = permission; + // now update all entries that refer to the old permission + HibernateCallback updateEntriesCallback = new HibernateCallback() + { + private static final int MAX_RESULTS = 1000; + @SuppressWarnings("unchecked") + public Object doInHibernate(Session session) + { + int count = 0; + while (true) + { + // flush any outstanding entities + session.flush(); + + Query query = session.getNamedQuery(HibernateHelper.QUERY_GET_ENTRIES_TO_CHANGE); + query.setParameter("oldTypeQName", oldTypeQName) + .setParameter("oldName", oldName) + .setMaxResults(MAX_RESULTS); + List entries = (List) query.list(); + // if there are no results, then we're done + if (entries.size() == 0) + { + break; + } + for (DbAccessControlEntry entry : entries) + { + entry.setPermission(newPermission); + count++; + session.evict(entry); + } + // flush and evict all the entries + session.flush(); + for (DbAccessControlEntry entry : entries) + { + session.evict(entry); + } + // next set of results + } + // done + return count; + } + }; + int updateCount = (Integer) getHibernateTemplate().execute(updateEntriesCallback); + // now delete the old permission + HibernateCallback getOldPermissionCallback = new GetPermissionCallback(oldTypeQName, oldName); + DbPermission oldPermission = (DbPermission) getHibernateTemplate().execute(getOldPermissionCallback); + if (oldPermission != null) + { + getHibernateTemplate().delete(oldPermission); + } + // done + return updateCount; + } + } +} diff --git a/source/java/org/alfresco/repo/admin/patch/impl/ActionRuleDecouplingPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/ActionRuleDecouplingPatch.java index 8f4aab96c8..71d86005a3 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/ActionRuleDecouplingPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/ActionRuleDecouplingPatch.java @@ -54,6 +54,10 @@ public class ActionRuleDecouplingPatch extends AbstractPatch for (NodeRef origRuleNodeRef : resultSet.getNodeRefs()) { // Check that this rule need updated + if (!this.nodeService.exists(origRuleNodeRef)) + { + continue; + } Map origProperties = this.nodeService.getProperties(origRuleNodeRef); if (origProperties.containsKey(RuleModel.PROP_EXECUTE_ASYNC) == false) { @@ -79,21 +83,21 @@ public class ActionRuleDecouplingPatch extends AbstractPatch Map newProperties = this.nodeService.getProperties(newRuleNodeRef); // Set the rule type, execute async and applyToChildren properties on the rule - String ruleType = (String)origProperties.get(RuleModel.PROP_RULE_TYPE); + Serializable ruleType = origProperties.get(RuleModel.PROP_RULE_TYPE); origProperties.remove(RuleModel.PROP_RULE_TYPE); newProperties.put(RuleModel.PROP_RULE_TYPE, ruleType); - Boolean executeAsync = (Boolean)origProperties.get(ActionModel.PROP_EXECUTE_ASYNCHRONOUSLY); + Serializable executeAsync = origProperties.get(ActionModel.PROP_EXECUTE_ASYNCHRONOUSLY); origProperties.remove(ActionModel.PROP_EXECUTE_ASYNCHRONOUSLY); newProperties.put(RuleModel.PROP_EXECUTE_ASYNC, executeAsync); - Boolean applyToChildren = (Boolean)origProperties.get(RuleModel.PROP_APPLY_TO_CHILDREN); + Serializable applyToChildren = origProperties.get(RuleModel.PROP_APPLY_TO_CHILDREN); origProperties.remove(RuleModel.PROP_APPLY_TO_CHILDREN); newProperties.put(RuleModel.PROP_APPLY_TO_CHILDREN, applyToChildren); origProperties.remove(QName.createQName(RuleModel.RULE_MODEL_URI, "owningNodeRef")); // Move the action and description values from the composite action onto the rule - String title = (String)origProperties.get(ActionModel.PROP_ACTION_TITLE); + Serializable title = origProperties.get(ActionModel.PROP_ACTION_TITLE); origProperties.remove(ActionModel.PROP_ACTION_TITLE); - String description = (String)origProperties.get(ActionModel.PROP_ACTION_DESCRIPTION); + Serializable description = origProperties.get(ActionModel.PROP_ACTION_DESCRIPTION); origProperties.remove(ActionModel.PROP_ACTION_DESCRIPTION); newProperties.put(ContentModel.PROP_TITLE, title); newProperties.put(ContentModel.PROP_DESCRIPTION, description); diff --git a/source/java/org/alfresco/repo/admin/patch/impl/ContentPermissionPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/ContentPermissionPatch.java index 6f428ccd92..031589d26c 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/ContentPermissionPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/ContentPermissionPatch.java @@ -16,33 +16,40 @@ */ package org.alfresco.repo.admin.patch.impl; -import org.alfresco.repo.admin.patch.AbstractPatch; -import org.alfresco.service.cmr.admin.PatchException; -import org.hibernate.SessionFactory; +import org.alfresco.i18n.I18NUtil; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; /** * Roles defined in permissionsDefinition.xml moved from cm:content to sys:base. - * This effects the data stored in the node_perm_entry table. - *

    - * WILL NOT EXECUTE ANYMORE + * This effects the data stored in the permission table. * * @author Derek Hulley */ -public class ContentPermissionPatch extends AbstractPatch +public class ContentPermissionPatch extends AbstractPermissionChangePatch { - private static final String MSG_UPGRADE = "patch.contentPermission.upgrade"; - - public ContentPermissionPatch() - { - } - - public void setSessionFactory(SessionFactory sessionFactory) - { - } + private static final String MSG_SUCCESS = "patch.contentPermission.result"; + private static final QName TYPE_QNAME_OLD = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "content"); + private static final QName TYPE_QNAME_NEW = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "base"); + private static final String[] NAMES = new String[] {"Execute", "ReadContent", "WriteContent", "ExecuteContent"}; + @Override protected String applyInternal() throws Exception { - throw new PatchException(MSG_UPGRADE); + int updateCount = 0; + for (String permissionName : NAMES) + { + updateCount += super.renamePermission( + ContentPermissionPatch.TYPE_QNAME_OLD, + permissionName, + ContentPermissionPatch.TYPE_QNAME_NEW, + permissionName); + } + + // build the result message + String msg = I18NUtil.getMessage(MSG_SUCCESS, updateCount); + // done + return msg; } } diff --git a/source/java/org/alfresco/repo/admin/patch/impl/PermissionDataPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/PermissionDataPatch.java index 8c815a7d1f..5d18fb4549 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/PermissionDataPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/PermissionDataPatch.java @@ -16,35 +16,42 @@ */ package org.alfresco.repo.admin.patch.impl; -import org.alfresco.repo.admin.patch.AbstractPatch; -import org.alfresco.service.cmr.admin.PatchException; -import org.hibernate.SessionFactory; +import org.alfresco.i18n.I18NUtil; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; /** * The roles defined in permissionsDefinition.xml moved from cm:folder to cm:cmobject. - * This effects the data stored in the node_perm_entry table. + * This effects the data stored in the permission table. *

    * JIRA: {@link http://www.alfresco.org/jira/browse/AR-344 AR-344} - *

    - * WILL NOT EXECUTE ANYMORE * * @author Derek Hulley */ -public class PermissionDataPatch extends AbstractPatch +public class PermissionDataPatch extends AbstractPermissionChangePatch { - private static final String MSG_UPGRADE = "patch.updatePermissionData.upgrade"; - - public PermissionDataPatch() - { - } - - public void setSessionFactory(SessionFactory sessionFactory) - { - } + private static final String MSG_SUCCESS = "patch.updatePermissionData.result"; + private static final QName TYPE_QNAME_OLD = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "folder"); + private static final QName TYPE_QNAME_NEW = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "cmobject"); + private static final String[] NAMES = new String[] {"Coordinator", "Contributor", "Editor", "Guest"}; + @Override protected String applyInternal() throws Exception { - throw new PatchException(MSG_UPGRADE); + int updateCount = 0; + for (String permissionName : NAMES) + { + updateCount += super.renamePermission( + PermissionDataPatch.TYPE_QNAME_OLD, + permissionName, + PermissionDataPatch.TYPE_QNAME_NEW, + permissionName); + } + + // build the result message + String msg = I18NUtil.getMessage(MSG_SUCCESS, updateCount); + // done + return msg; } } diff --git a/source/java/org/alfresco/repo/admin/patch/impl/UniqueChildNamePatch.java b/source/java/org/alfresco/repo/admin/patch/impl/UniqueChildNamePatch.java index 28a77aadd3..4fde56eac5 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/UniqueChildNamePatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/UniqueChildNamePatch.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; +import java.util.Collection; import java.util.Date; import java.util.List; @@ -30,6 +31,7 @@ import org.alfresco.repo.admin.patch.AbstractPatch; import org.alfresco.repo.domain.ChildAssoc; import org.alfresco.repo.domain.Node; import org.alfresco.repo.node.db.NodeDaoService; +import org.alfresco.service.cmr.admin.PatchException; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; @@ -51,6 +53,7 @@ import org.springframework.orm.hibernate3.support.HibernateDaoSupport; public class UniqueChildNamePatch extends AbstractPatch { private static final String MSG_SUCCESS = "patch.uniqueChildName.result"; + private static final String ERR_UNABLE_TO_FIX = "patch.uniqueChildName.err.unable_to_fix"; private static final String MSG_COPY_OF = "patch.uniqueChildName.copyOf"; /** the number of associations to process at a time */ private static final int MAX_RESULTS = 1000; @@ -143,6 +146,7 @@ public class UniqueChildNamePatch extends AbstractPatch @SuppressWarnings("unused") List assocTypeQNames = getUsedAssocQNames(); + boolean unableToFix = false; int fixed = 0; int processed = 0; // check loop through all associations, looking for duplicates @@ -185,8 +189,10 @@ public class UniqueChildNamePatch extends AbstractPatch String usedChildName = childName; processed++; boolean duplicate = false; + int duplicateNumber = 0; while(true) { + duplicateNumber++; try { // push the name back to the node @@ -195,11 +201,46 @@ public class UniqueChildNamePatch extends AbstractPatch } catch (DuplicateChildNodeNameException e) { - // there was a duplicate, so adjust the name and change the node property - duplicate = true; - // assign a new name - usedChildName = childName + I18NUtil.getMessage(MSG_COPY_OF, processed); - // try again + if (duplicateNumber == 10) + { + // Try removing the secondary parent associations + writeLine(" Removing secondary parents of node " + childNode.getId()); + Collection parentAssocs = childNode.getParentAssocs(); + for (ChildAssoc parentAssoc : parentAssocs) + { + if (!parentAssoc.getIsPrimary()) + { + write(" - ").writeLine(parentAssoc); + // remove it + getSession().delete(parentAssoc); + } + } + // flush to ensure the database gets the changes + getSession().flush(); + // try again to be sure + continue; + } + else if (duplicateNumber > 10) + { + // after 10 attempts, we have to admit defeat. Perhaps there is a larger issue. + Collection parentAssocs = childNode.getParentAssocs(); + write(" Unable to set child name '" + usedChildName + "' for node " + childNode.getId()); + writeLine(" with parent associations:"); + for (ChildAssoc parentAssoc : parentAssocs) + { + write(" - ").writeLine(parentAssoc); + } + duplicate = false; + unableToFix = true; + break; + } + else + { + // there was a duplicate, so adjust the name and change the node property + duplicate = true; + // assign a new name + usedChildName = childName + I18NUtil.getMessage(MSG_COPY_OF, processed, duplicateNumber); + } } } // if duplicated, report it @@ -209,11 +250,11 @@ public class UniqueChildNamePatch extends AbstractPatch // get the node path NodeRef parentNodeRef = childAssoc.getParent().getNodeRef(); Path path = nodeService.getPath(parentNodeRef); - writeLine(" Changed duplicated child name:"); - writeLine(" Parent: " + parentNodeRef); - writeLine(" Parent path: " + path); - writeLine(" Duplicate name: " + childName); - writeLine(" Replaced with: " + usedChildName); + writeLine(" Changed duplicated child name:"); + writeLine(" Parent: " + parentNodeRef); + writeLine(" Parent path: " + path); + writeLine(" Duplicate name: " + childName); + writeLine(" Replaced with: " + usedChildName); } } // clear the session to preserve memory @@ -222,10 +263,17 @@ public class UniqueChildNamePatch extends AbstractPatch } } - - // build the result message - String msg = I18NUtil.getMessage(MSG_SUCCESS, processed, fixed, logFile); - return msg; + // check if it was successful or not + if (unableToFix) + { + throw new PatchException(ERR_UNABLE_TO_FIX, logFile); + } + else + { + // build the result message + String msg = I18NUtil.getMessage(MSG_SUCCESS, processed, fixed, logFile); + return msg; + } } @SuppressWarnings("unchecked") diff --git a/source/java/org/alfresco/repo/admin/patch/impl/UpdateGuestPermissionPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/UpdateGuestPermissionPatch.java index 58fd936812..374a393835 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/UpdateGuestPermissionPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/UpdateGuestPermissionPatch.java @@ -16,32 +16,27 @@ */ package org.alfresco.repo.admin.patch.impl; -import org.alfresco.repo.admin.patch.AbstractPatch; -import org.alfresco.service.cmr.admin.PatchException; -import org.hibernate.SessionFactory; +import org.alfresco.i18n.I18NUtil; +import org.alfresco.model.ContentModel; /** * The permission 'Guest' has been renamed to 'Consumer'. - *

    - * WILL NOT EXECUTE ANYMORE * * @author David Caruana + * @author Derek Hulley */ -public class UpdateGuestPermissionPatch extends AbstractPatch +public class UpdateGuestPermissionPatch extends AbstractPermissionChangePatch { - private static final String MSG_UPGRADE = "patch.updateGuestPermission.upgrade"; - - public UpdateGuestPermissionPatch() - { - } - - public void setSessionFactory(SessionFactory sessionFactory) - { - } + private static final String MSG_SUCCESS = "patch.updateGuestPermission.result"; @Override protected String applyInternal() throws Exception { - throw new PatchException(MSG_UPGRADE); + int updateCount = super.renamePermission(ContentModel.TYPE_CMOBJECT, "Guest", ContentModel.TYPE_CMOBJECT, "Consumer"); + + // build the result message + String msg = I18NUtil.getMessage(MSG_SUCCESS, updateCount); + // done + return msg; } } diff --git a/source/java/org/alfresco/repo/admin/patch/util/ImportFileUpdater.java b/source/java/org/alfresco/repo/admin/patch/util/ImportFileUpdater.java new file mode 100644 index 0000000000..4148f74de8 --- /dev/null +++ b/source/java/org/alfresco/repo/admin/patch/util/ImportFileUpdater.java @@ -0,0 +1,573 @@ +/** + * + */ +package org.alfresco.repo.admin.patch.util; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ActionModel; +import org.alfresco.repo.rule.RuleModel; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.util.GUID; +import org.dom4j.io.OutputFormat; +import org.dom4j.io.XMLWriter; +import org.xml.sax.helpers.AttributesImpl; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +/** + * Updates a XML import file to be compatable with the current version of the repository. + * + * @author royw + */ +public class ImportFileUpdater +{ + /** Indent size **/ + private static int INDENT_SIZE = 2; + + /** The destination export version number **/ + private static String EXPORT_VERSION = "1.4.0"; + + /** Element names **/ + private static String NAME_EXPORTER_VERSION = "exporterVersion"; + private static String NAME_RULE = "rule"; + + /** The current import version number **/ + private String version; + + /** + * Updates the passed import file into the equivalent 1.4 format. + * + * @param source the source import file + * @param destination the destination import file + */ + public void updateImportFile(String source, String destination) + { + XmlPullParser reader = getReader(source); + XMLWriter writer = getWriter(destination); + + try + { + // Start the documentation + writer.startDocument(); + + // Start reading the document + int eventType = reader.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) + { + if (eventType == XmlPullParser.START_TAG) + { + ImportFileUpdater.this.outputCurrentElement(reader, writer, new OutputChildren()); + } + eventType = reader.next(); + } + + // End and close the document + writer.endDocument(); + writer.close(); + } + catch (Exception exception) + { + throw new AlfrescoRuntimeException("Unable to update import file.", exception); + } + + } + + /** + * Get the reader for the source import file + * + * @param source the source import file + * @return the XML pull parser used to read the file + */ + private XmlPullParser getReader(String source) + { + try + { + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null); + factory.setNamespaceAware(true); + + XmlPullParser xpp = factory.newPullParser(); + xpp.setInput(new FileReader(source)); + + return xpp; + } + catch (XmlPullParserException exception) + { + throw new AlfrescoRuntimeException("Unable to update import file.", exception); + } + catch (FileNotFoundException fileNotFound) + { + throw new AlfrescoRuntimeException("The source file could not be loaded.", fileNotFound); + } + } + + /** + * Get the writer for the import file + * + * @param destination the destination XML import file + * @return the XML writer + */ + private XMLWriter getWriter(String destination) + { + try + { + // Define output format + OutputFormat format = OutputFormat.createPrettyPrint(); + format.setNewLineAfterDeclaration(false); + format.setIndentSize(INDENT_SIZE); + format.setEncoding("UTF-8"); + + return new XMLWriter(new FileOutputStream(destination), format); + } + catch (Exception exception) + { + throw new AlfrescoRuntimeException("Unable to create XML writer.", exception); + } + } + + private void outputCurrentElement(XmlPullParser reader, XMLWriter writer, Work work) + throws Exception + { + outputCurrentElement(reader, writer, work, true); + } + + private void outputCurrentElement(XmlPullParser reader, XMLWriter writer, Work work, boolean checkForCallbacks) + throws Exception + { + if (checkForCallbacks == false || checkForCallbacks(reader, writer) == false) + { + // Get the name details of the element + String name = reader.getName(); + String namespace = reader.getNamespace(); + String prefix = reader.getPrefix(); + + // Sort out namespaces + Map nss = new HashMap(); + int nsStart = reader.getNamespaceCount(reader.getDepth()-1); + int nsEnd = reader.getNamespaceCount(reader.getDepth()); + for (int i = nsStart; i < nsEnd; i++) + { + String nsPrefix = reader.getNamespacePrefix(i); + String ns = reader.getNamespaceUri(i); + nss.put(nsPrefix, ns); + } + + // Sort out attributes + AttributesImpl attributes = new AttributesImpl(); + for (int i = 0; i < reader.getAttributeCount(); i++) + { + String attributeName = reader.getAttributeName(i); + String attributeNamespace = reader.getAttributeNamespace(i); + String attributePrefix = reader.getAttributePrefix(i); + String attributeType = reader.getAttributeType(i); + String attributeValue = reader.getAttributeValue(i); + + attributes.addAttribute(attributeNamespace, attributeName, attributePrefix+":"+attributeName, attributeType, attributeValue); + } + + // Start the namespace prefixes + for (Map.Entry entry : nss.entrySet()) + { + writer.startPrefixMapping(entry.getKey(), entry.getValue()); + } + + // Write the start of the element + writer.startElement(namespace, name, prefix+":"+name, attributes); + + // Do the work + work.doWork(reader, writer); + + // Write the end of the element + writer.endElement(namespace, name, prefix+":"+name); + + // End the namespace prefixes + for (String nsPrefix : nss.keySet()) + { + writer.endPrefixMapping(nsPrefix); + } + } + } + + private boolean checkForCallbacks(XmlPullParser reader, XMLWriter writer) + throws Exception + { + boolean result = false; + if (reader.getName().equals(NAME_EXPORTER_VERSION) == true) + { + new ImportVersionLabelCallback().doCallback(reader, writer); + result = true; + } + else if (reader.getName().equals(NAME_RULE) == true) + { + if (this.version.startsWith("1.3") == true) + { + new RuleCallback().doCallback(reader, writer); + result = true; + } + } + return result; + } + + private interface Work + { + void doWork(XmlPullParser reader, XMLWriter writer) + throws Exception; + } + + private class OutputChildren implements Work + { + public void doWork(XmlPullParser reader, XMLWriter writer) + throws Exception + { + // Deal with the contents of the tag + int eventType = reader.getEventType(); + while (eventType != XmlPullParser.END_TAG) + { + eventType = reader.next(); + if (eventType == XmlPullParser.START_TAG) + { + ImportFileUpdater.this.outputCurrentElement(reader, writer, new OutputChildren()); + } + else if (eventType == XmlPullParser.TEXT) + { + // Write the text to the output file + writer.write(reader.getText()); + } + } + } + } + + @SuppressWarnings("unused") + private class IgnoreChildren implements Work + { + public void doWork(XmlPullParser reader, XMLWriter writer) + throws Exception + { + int eventType = reader.getEventType(); + while (eventType != XmlPullParser.END_TAG) + { + eventType = reader.next(); + if (eventType == XmlPullParser.START_TAG) + { + doWork(reader, writer); + } + } + } + } + + private interface ImportUpdaterCallback + { + void doCallback(XmlPullParser reader, XMLWriter writer) + throws Exception; + } + + private class ImportVersionLabelCallback implements ImportUpdaterCallback + { + public void doCallback(XmlPullParser reader, XMLWriter writer) + throws Exception + { + ImportFileUpdater.this.outputCurrentElement(reader, writer, + new Work() + { + public void doWork(XmlPullParser reader, XMLWriter writer) throws Exception + { + reader.next(); + ImportFileUpdater.this.version = reader.getText(); + writer.write(EXPORT_VERSION); + reader.next(); + } + }, false); + } + } + + private class RuleCallback implements ImportUpdaterCallback + { + public void doCallback(XmlPullParser reader, XMLWriter writer) + throws Exception + { + // Get the name details of the element + String name = reader.getName(); + String namespace = reader.getNamespace(); + String prefix = reader.getPrefix(); + + // Rename the child assoc appropriately + AttributesImpl attributes = new AttributesImpl(); + String attributeName = reader.getAttributeName(0); + String attributeNamespace = reader.getAttributeNamespace(0); + String attributePrefix = reader.getAttributePrefix(0); + String attributeType = reader.getAttributeType(0); + String attributeValue = reader.getAttributeValue(0) + GUID.generate(); + attributes.addAttribute(attributeNamespace, attributeName, attributePrefix+":"+attributeName, attributeType, attributeValue); + + // Output the rules element + writer.startElement(namespace, name, prefix+":"+name, attributes); + + int eventType = reader.getEventType(); + while (eventType != XmlPullParser.END_TAG) + { + eventType = reader.next(); + if (eventType == XmlPullParser.START_TAG) + { + String childName = reader.getName(); + if (childName.equals("aspects") == true) + { + ImportFileUpdater.this.outputCurrentElement(reader, writer, + new Work() + { + public void doWork(XmlPullParser reader, XMLWriter writer) throws Exception + { + // Add titled aspect + writer.startElement( + ContentModel.ASPECT_TITLED.getNamespaceURI(), + ContentModel.ASPECT_TITLED.getLocalName(), + NamespaceService.CONTENT_MODEL_PREFIX + ":" + ContentModel.ASPECT_TITLED.getLocalName(), + new AttributesImpl()); + writer.endElement( + ContentModel.ASPECT_TITLED.getNamespaceURI(), + ContentModel.ASPECT_TITLED.getLocalName(), + NamespaceService.CONTENT_MODEL_PREFIX + ":" + ContentModel.ASPECT_TITLED.getLocalName()); + + // Read the rest of the elements and output + int eventType = reader.getEventType(); + while (eventType != XmlPullParser.END_TAG) + { + eventType = reader.next(); + if (eventType == XmlPullParser.START_TAG) + { + ImportFileUpdater.this.outputCurrentElement(reader, writer, new OutputChildren()); + } + } + } + + }, false); + } + else if (childName.equals("properties") == true) + { + ImportFileUpdater.this.outputCurrentElement(reader, writer, + new Work() + { + public void doWork(XmlPullParser reader, XMLWriter writer) throws Exception + { + int eventType = reader.getEventType(); + while (eventType != XmlPullParser.END_TAG) + { + eventType = reader.next(); + if (eventType == XmlPullParser.START_TAG) + { + String propName = reader.getName(); + if (propName.equals("actionDescription") == true) + { + writer.startElement( + ContentModel.PROP_DESCRIPTION.getNamespaceURI(), + ContentModel.PROP_DESCRIPTION.getLocalName(), + NamespaceService.CONTENT_MODEL_PREFIX + ":" + ContentModel.PROP_DESCRIPTION.getLocalName(), + new AttributesImpl()); + + // Output the value within + new OutputChildren().doWork(reader, writer); + + writer.endElement( + ContentModel.PROP_DESCRIPTION.getNamespaceURI(), + ContentModel.PROP_DESCRIPTION.getLocalName(), + NamespaceService.CONTENT_MODEL_PREFIX + ":" + ContentModel.PROP_DESCRIPTION.getLocalName()); + eventType = reader.next(); + + } + else if (propName.equals("actionTitle") == true) + { + writer.startElement( + ContentModel.PROP_TITLE.getNamespaceURI(), + ContentModel.PROP_TITLE.getLocalName(), + NamespaceService.CONTENT_MODEL_PREFIX + ":" + ContentModel.PROP_TITLE.getLocalName(), + new AttributesImpl()); + + // Output the value within + new OutputChildren().doWork(reader, writer); + + writer.endElement( + ContentModel.PROP_TITLE.getNamespaceURI(), + ContentModel.PROP_TITLE.getLocalName(), + NamespaceService.CONTENT_MODEL_PREFIX + ":" + ContentModel.PROP_TITLE.getLocalName()); + eventType = reader.next(); + } + else if (propName.equals("executeAsynchronously") == true) + { + writer.startElement( + RuleModel.PROP_EXECUTE_ASYNC.getNamespaceURI(), + RuleModel.PROP_EXECUTE_ASYNC.getLocalName(), + RuleModel.RULE_MODEL_PREFIX + ":" + RuleModel.PROP_EXECUTE_ASYNC.getLocalName(), + new AttributesImpl()); + + // Output the value within + new OutputChildren().doWork(reader, writer); + + writer.endElement( + RuleModel.PROP_EXECUTE_ASYNC.getNamespaceURI(), + RuleModel.PROP_EXECUTE_ASYNC.getLocalName(), + RuleModel.RULE_MODEL_PREFIX + ":" + RuleModel.PROP_EXECUTE_ASYNC.getLocalName()); + eventType = reader.next(); + } + else if (propName.equals("ruleType") == true) + { + ImportFileUpdater.this.outputCurrentElement(reader, writer, + new Work() + { + public void doWork(XmlPullParser reader, XMLWriter writer) throws Exception + { + // Output the elements that contain a multi values property + writer.startElement(NamespaceService.REPOSITORY_VIEW_1_0_URI, "values", "view:values", new AttributesImpl()); + writer.startElement(NamespaceService.REPOSITORY_VIEW_1_0_URI, "value", "view:value", new AttributesImpl()); + + // Output the value within + new OutputChildren().doWork(reader, writer); + + // End the multi values elements + writer.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "value", "view:value"); + writer.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "values", "view:values"); + } + }, false); + } + else if (propName.equals("definitionName") == true) + { + // Skip past next end + while (eventType != XmlPullParser.END_TAG) + { + eventType = reader.next(); + } + eventType = reader.next(); + } + else + { + ImportFileUpdater.this.outputCurrentElement(reader, writer, new OutputChildren()); + } + } + } + + // Output value for the disabled property + writer.startElement( + RuleModel.PROP_DISABLED.getNamespaceURI(), + RuleModel.PROP_DISABLED.getLocalName(), + RuleModel.RULE_MODEL_PREFIX + ":" + RuleModel.PROP_DISABLED.getLocalName(), + new AttributesImpl()); + writer.write("false"); + writer.endElement( + RuleModel.PROP_DISABLED.getNamespaceURI(), + RuleModel.PROP_DISABLED.getLocalName(), + RuleModel.RULE_MODEL_PREFIX + ":" + RuleModel.PROP_DISABLED.getLocalName()); + } + }, false); + } + else if (childName.equals("associations") == true) + { + ImportFileUpdater.this.outputCurrentElement(reader, writer, + new Work() + { + public void doWork(XmlPullParser reader, XMLWriter writer) throws Exception + { + // + writer.startElement( + RuleModel.ASSOC_ACTION.getNamespaceURI(), + RuleModel.ASSOC_ACTION.getLocalName(), + RuleModel.RULE_MODEL_PREFIX + ":" + RuleModel.ASSOC_ACTION.getLocalName(), + new AttributesImpl()); + + // + AttributesImpl attributes = new AttributesImpl(); + attributes.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, "childName", "view:childName", null, "rule:action"); + writer.startElement( + ActionModel.TYPE_COMPOSITE_ACTION.getNamespaceURI(), + ActionModel.TYPE_COMPOSITE_ACTION.getLocalName(), + ActionModel.ACTION_MODEL_PREFIX+ ":" + ActionModel.TYPE_COMPOSITE_ACTION.getLocalName(), + attributes); + + // + writer.startElement( + NamespaceService.REPOSITORY_VIEW_1_0_URI, + "properties", + "view:properties", + new AttributesImpl()); + + // composite-action + writer.startElement( + ActionModel.PROP_DEFINITION_NAME.getNamespaceURI(), + ActionModel.PROP_DEFINITION_NAME.getLocalName(), + ActionModel.ACTION_MODEL_PREFIX + ":" + ActionModel.PROP_DEFINITION_NAME.getLocalName(), + new AttributesImpl()); + writer.write("composite-action"); + writer.endElement( + ActionModel.PROP_DEFINITION_NAME.getNamespaceURI(), + ActionModel.PROP_DEFINITION_NAME.getLocalName(), + ActionModel.ACTION_MODEL_PREFIX + ":" + ActionModel.PROP_DEFINITION_NAME.getLocalName()); + + // + writer.endElement( + NamespaceService.REPOSITORY_VIEW_1_0_URI, + "properties", + "view:properties"); + + // + writer.startElement( + NamespaceService.REPOSITORY_VIEW_1_0_URI, + "associations", + "view:associations", + new AttributesImpl()); + + // Output the association details + new OutputChildren().doWork(reader, writer); + + // + writer.endElement( + NamespaceService.REPOSITORY_VIEW_1_0_URI, + "associations", + "view:associations"); + + // + writer.endElement( + ActionModel.TYPE_COMPOSITE_ACTION.getNamespaceURI(), + ActionModel.TYPE_COMPOSITE_ACTION.getLocalName(), + ActionModel.ACTION_MODEL_PREFIX+ ":" + ActionModel.TYPE_COMPOSITE_ACTION.getLocalName()); + + // + writer.endElement( + RuleModel.ASSOC_ACTION.getNamespaceURI(), + RuleModel.ASSOC_ACTION.getLocalName(), + RuleModel.RULE_MODEL_PREFIX + ":" + RuleModel.ASSOC_ACTION.getLocalName()); + } + }, false); + } + else + { + // Output anything else that might be hanging araound + ImportFileUpdater.this.outputCurrentElement(reader, writer, new OutputChildren()); + } + } + } + + // End the rules element + writer.endElement(namespace, name, prefix+":"+name); + } + } + + public static void main(String[] args) + { + if (args.length == 2) + { + ImportFileUpdater util = new ImportFileUpdater(); + util.updateImportFile(args[0], args[1]); + } + else + { + System.out.println(" ImportFileUpdater "); + System.out.println(" source - 1.3 import file name to be updated"); + System.out.println(" destination - name of the generated 1.4 import file"); + } + } + +} diff --git a/source/java/org/alfresco/repo/content/RoutingContentService.java b/source/java/org/alfresco/repo/content/RoutingContentService.java index 3821025eca..4137a3a7ea 100644 --- a/source/java/org/alfresco/repo/content/RoutingContentService.java +++ b/source/java/org/alfresco/repo/content/RoutingContentService.java @@ -1,506 +1,506 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.repo.content; - -import java.io.Serializable; -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.avm.AVMNodeConverter; -import org.alfresco.repo.content.ContentServicePolicies.OnContentReadPolicy; -import org.alfresco.repo.content.ContentServicePolicies.OnContentUpdatePolicy; -import org.alfresco.repo.content.filestore.FileContentStore; -import org.alfresco.repo.content.transform.ContentTransformer; -import org.alfresco.repo.content.transform.ContentTransformerRegistry; -import org.alfresco.repo.content.transform.magick.ImageMagickContentTransformer; -import org.alfresco.repo.policy.ClassPolicyDelegate; -import org.alfresco.repo.policy.JavaBehaviour; -import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.service.cmr.avm.AVMService; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.dictionary.InvalidTypeException; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.ContentIOException; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentService; -import org.alfresco.service.cmr.repository.ContentStreamListener; -import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.repository.NoTransformerException; -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.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.EqualsHelper; -import org.alfresco.util.Pair; -import org.alfresco.util.TempFileProvider; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - - -/** - * A content service that determines at runtime the store that the - * content associated with a node should be routed to. - * - * @author Derek Hulley - */ -public class RoutingContentService implements ContentService -{ - private static Log logger = LogFactory.getLog(RoutingContentService.class); - - private TransactionService transactionService; - private DictionaryService dictionaryService; - private NodeService nodeService; - private AVMService avmService; - - /** a registry of all available content transformers */ - private ContentTransformerRegistry transformerRegistry; - /** TEMPORARY until we have a map to choose from at runtime */ - private ContentStore store; - /** the store for all temporarily created content */ - private ContentStore tempStore; - private ImageMagickContentTransformer imageMagickContentTransformer; - - /** - * The policy component - */ - private PolicyComponent policyComponent; - - /** - * Policies delegate - */ - ClassPolicyDelegate onContentUpdateDelegate; - ClassPolicyDelegate onContentReadDelegate; - - /** - * Default constructor sets up a temporary store - */ - public RoutingContentService() - { - this.tempStore = new FileContentStore(TempFileProvider.getTempDir().getAbsolutePath()); - } - - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setTransformerRegistry(ContentTransformerRegistry transformerRegistry) - { - this.transformerRegistry = transformerRegistry; - } - - public void setStore(ContentStore store) - { - this.store = store; - } - - public void setPolicyComponent(PolicyComponent policyComponent) - { - this.policyComponent = policyComponent; - } - - public void setAvmService(AVMService service) - { - this.avmService = service; - } - - public void setImageMagickContentTransformer(ImageMagickContentTransformer imageMagickContentTransformer) - { - this.imageMagickContentTransformer = imageMagickContentTransformer; - } - - /** - * Service initialise - */ - public void init() - { - // Bind on update properties behaviour - this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"), - this, - new JavaBehaviour(this, "onUpdateProperties")); - - // Register on content update policy - this.onContentUpdateDelegate = this.policyComponent.registerClassPolicy(OnContentUpdatePolicy.class); - this.onContentReadDelegate = this.policyComponent.registerClassPolicy(OnContentReadPolicy.class); - } - - /** - * Update properties policy behaviour - * - * @param nodeRef the node reference - * @param before the before values of the properties - * @param after the after values of the properties - */ - public void onUpdateProperties( - NodeRef nodeRef, - Map before, - Map after) - { - boolean fire = false; - boolean newContent = false; - // check if any of the content properties have changed - for (QName propertyQName : after.keySet()) - { - // is this a content property? - PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); - if (propertyDef == null) - { - // the property is not recognised - continue; - } - if (!propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT)) - { - // not a content type - continue; - } - - try - { - ContentData beforeValue = (ContentData) before.get(propertyQName); - ContentData afterValue = (ContentData) after.get(propertyQName); - if (afterValue != null && afterValue.getContentUrl() == null) - { - // no URL - ignore - } - else if (!EqualsHelper.nullSafeEquals(beforeValue, afterValue)) - { - // So debug ... - if (logger.isDebugEnabled() == true) - { - String beforeString = ""; - if (beforeValue != null) - { - beforeString = beforeValue.toString(); - } - String afterString = ""; - if (afterValue != null) - { - afterString = afterValue.toString(); - } - logger.debug("onContentUpate: before = " + beforeString + "; after = " + afterString); - } - - // Figure out if the content is new or not - String beforeContentUrl = null; - if (beforeValue != null) - { - beforeContentUrl = beforeValue.getContentUrl(); - } - String afterContentUrl = null; - if (afterValue != null) - { - afterContentUrl = afterValue.getContentUrl(); - } - if (beforeContentUrl == null && afterContentUrl != null) - { - newContent = true; - } - - // the content changed - // at the moment, we are only interested in this one change - fire = true; - break; - } - } - catch (ClassCastException e) - { - // properties don't conform to model - continue; - } - } - // fire? - if (fire) - { - // Fire the content update policy - Set types = new HashSet(this.nodeService.getAspects(nodeRef)); - types.add(this.nodeService.getType(nodeRef)); - OnContentUpdatePolicy policy = this.onContentUpdateDelegate.get(nodeRef, types); - policy.onContentUpdate(nodeRef, newContent); - } - } - - public ContentReader getReader(NodeRef nodeRef, QName propertyQName) - { - return getReader(nodeRef, propertyQName, true); - } - - private ContentReader getReader(NodeRef nodeRef, QName propertyQName, boolean fireContentReadPolicy) - { - ContentData contentData = null; - Serializable propValue = nodeService.getProperty(nodeRef, propertyQName); - if (propValue instanceof Collection) - { - Collection colPropValue = (Collection)propValue; - if (colPropValue.size() > 0) - { - propValue = (Serializable)colPropValue.iterator().next(); - } - } - - if (propValue instanceof ContentData) - { - contentData = (ContentData)propValue; - } - - if (contentData == null) - { - // if no value or a value other content, and a property definition has been provided, ensure that it's CONTENT or ANY - PropertyDefinition contentPropDef = dictionaryService.getProperty(propertyQName); - if (contentPropDef != null && - (!(contentPropDef.getDataType().getName().equals(DataTypeDefinition.CONTENT) || - contentPropDef.getDataType().getName().equals(DataTypeDefinition.ANY)))) - { - throw new InvalidTypeException("The node property must be of type content: \n" + - " node: " + nodeRef + "\n" + - " property name: " + propertyQName + "\n" + - " property type: " + ((contentPropDef == null) ? "unknown" : contentPropDef.getDataType()), - propertyQName); - } - } - - // check that the URL is available - if (contentData == null || contentData.getContentUrl() == null) - { - // there is no URL - the interface specifies that this is not an error condition - return null; - } - String contentUrl = contentData.getContentUrl(); - - // TODO: Choose the store to read from at runtime - ContentReader reader = store.getReader(contentUrl); - - // set extra data on the reader - reader.setMimetype(contentData.getMimetype()); - reader.setEncoding(contentData.getEncoding()); - - // Fire the content read policy - if (reader != null && fireContentReadPolicy == true) - { - // Fire the content update policy - Set types = new HashSet(this.nodeService.getAspects(nodeRef)); - types.add(this.nodeService.getType(nodeRef)); - OnContentReadPolicy policy = this.onContentReadDelegate.get(nodeRef, types); - policy.onContentRead(nodeRef); - } - - // we don't listen for anything - // result may be null - but interface contract says we may return null - return reader; - } - - public ContentWriter getWriter(NodeRef nodeRef, QName propertyQName, boolean update) - { - // check for an existing URL - the get of the reader will perform type checking - ContentReader existingContentReader = getReader(nodeRef, propertyQName, false); - - // TODO: Choose the store to write to at runtime - - // get the content using the (potentially) existing content - the new content - // can be wherever the store decides. - ContentWriter writer = store.getWriter(existingContentReader, null); - - // Special case for AVM repository. - Serializable contentValue = null; - if (nodeRef.getStoreRef().getProtocol().equals(StoreRef.PROTOCOL_AVM)) - { - Pair avmVersionPath = AVMNodeConverter.ToAVMVersionPath(nodeRef); - contentValue = avmService.getContentDataForWrite(avmVersionPath.getSecond()); - } - else - { - contentValue = nodeService.getProperty(nodeRef, propertyQName); - } - - // set extra data on the reader if the property is pre-existing - if (contentValue != null && contentValue instanceof ContentData) - { - ContentData contentData = (ContentData)contentValue; - writer.setMimetype(contentData.getMimetype()); - writer.setEncoding(contentData.getEncoding()); - } - - // attach a listener if required - if (update) - { - // need a listener to update the node when the stream closes - WriteStreamListener listener = new WriteStreamListener(nodeService, nodeRef, propertyQName, writer); - writer.addListener(listener); - writer.setTransactionService(transactionService); - } - - // give back to the client - return writer; - } - - /** - * @return Returns a writer to an anonymous location - */ - public ContentWriter getTempWriter() - { - // there is no existing content and we don't specify the location of the new content - return tempStore.getWriter(null, null); - } - - /** - * @see org.alfresco.repo.content.transform.ContentTransformerRegistry - * @see org.alfresco.repo.content.transform.ContentTransformer - */ - public void transform(ContentReader reader, ContentWriter writer) - throws NoTransformerException, ContentIOException - { - // check that source and target mimetypes are available - String sourceMimetype = reader.getMimetype(); - if (sourceMimetype == null) - { - throw new AlfrescoRuntimeException("The content reader mimetype must be set: " + reader); - } - String targetMimetype = writer.getMimetype(); - if (targetMimetype == null) - { - throw new AlfrescoRuntimeException("The content writer mimetype must be set: " + writer); - } - // look for a transformer - ContentTransformer transformer = transformerRegistry.getTransformer(sourceMimetype, targetMimetype); - if (transformer == null) - { - throw new NoTransformerException(sourceMimetype, targetMimetype); - } - // we have a transformer, so do it - transformer.transform(reader, writer); - // done - } - - /** - * @see org.alfresco.repo.content.transform.ContentTransformerRegistry - * @see org.alfresco.repo.content.transform.ContentTransformer - */ - public ContentTransformer getTransformer(String sourceMimetype, String targetMimetype) - { - // look for a transformer - ContentTransformer transformer = transformerRegistry.getTransformer(sourceMimetype, targetMimetype); - // done - return transformer; - } - - /** - * @see org.alfresco.service.cmr.repository.ContentService#getImageTransformer() - */ - public ContentTransformer getImageTransformer() - { - return imageMagickContentTransformer; - } - - /** - * @see org.alfresco.repo.content.transform.ContentTransformerRegistry - * @see org.alfresco.repo.content.transform.ContentTransformer - */ - public boolean isTransformable(ContentReader reader, ContentWriter writer) - { - // check that source and target mimetypes are available - String sourceMimetype = reader.getMimetype(); - if (sourceMimetype == null) - { - throw new AlfrescoRuntimeException("The content reader mimetype must be set: " + reader); - } - String targetMimetype = writer.getMimetype(); - if (targetMimetype == null) - { - throw new AlfrescoRuntimeException("The content writer mimetype must be set: " + writer); - } - - // look for a transformer - ContentTransformer transformer = transformerRegistry.getTransformer(sourceMimetype, targetMimetype); - return (transformer != null); - } - - /** - * Ensures that, upon closure of the output stream, the node is updated with - * the latest URL of the content to which it refers. - *

    - * The listener close operation does not need a transaction as the - * ContentWriter takes care of that. - * - * @author Derek Hulley - */ - private static class WriteStreamListener implements ContentStreamListener - { - private NodeService nodeService; - private NodeRef nodeRef; - private QName propertyQName; - private ContentWriter writer; - - public WriteStreamListener( - NodeService nodeService, - NodeRef nodeRef, - QName propertyQName, - ContentWriter writer) - { - this.nodeService = nodeService; - this.nodeRef = nodeRef; - this.propertyQName = propertyQName; - this.writer = writer; - } - - public void contentStreamClosed() throws ContentIOException - { - try - { - // set the full content property - ContentData contentData = writer.getContentData(); - nodeService.setProperty( - nodeRef, - propertyQName, - contentData); - // done - if (logger.isDebugEnabled()) - { - logger.debug("Stream listener updated node: \n" + - " node: " + nodeRef + "\n" + - " property: " + propertyQName + "\n" + - " value: " + contentData); - } - } - catch (Throwable e) - { - throw new ContentIOException("Failed to set content property on stream closure: \n" + - " node: " + nodeRef + "\n" + - " property: " + propertyQName + "\n" + - " writer: " + writer, - e); - } - } - } -} +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.content; + +import java.io.Serializable; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.avm.AVMNodeConverter; +import org.alfresco.repo.content.ContentServicePolicies.OnContentReadPolicy; +import org.alfresco.repo.content.ContentServicePolicies.OnContentUpdatePolicy; +import org.alfresco.repo.content.filestore.FileContentStore; +import org.alfresco.repo.content.transform.ContentTransformer; +import org.alfresco.repo.content.transform.ContentTransformerRegistry; +import org.alfresco.repo.content.transform.magick.ImageMagickContentTransformer; +import org.alfresco.repo.policy.ClassPolicyDelegate; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.service.cmr.avm.AVMService; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.InvalidTypeException; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentStreamListener; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NoTransformerException; +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.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.EqualsHelper; +import org.alfresco.util.Pair; +import org.alfresco.util.TempFileProvider; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * A content service that determines at runtime the store that the + * content associated with a node should be routed to. + * + * @author Derek Hulley + */ +public class RoutingContentService implements ContentService +{ + private static Log logger = LogFactory.getLog(RoutingContentService.class); + + private TransactionService transactionService; + private DictionaryService dictionaryService; + private NodeService nodeService; + private AVMService avmService; + + /** a registry of all available content transformers */ + private ContentTransformerRegistry transformerRegistry; + /** TEMPORARY until we have a map to choose from at runtime */ + private ContentStore store; + /** the store for all temporarily created content */ + private ContentStore tempStore; + private ImageMagickContentTransformer imageMagickContentTransformer; + + /** + * The policy component + */ + private PolicyComponent policyComponent; + + /** + * Policies delegate + */ + ClassPolicyDelegate onContentUpdateDelegate; + ClassPolicyDelegate onContentReadDelegate; + + /** + * Default constructor sets up a temporary store + */ + public RoutingContentService() + { + this.tempStore = new FileContentStore(TempFileProvider.getTempDir().getAbsolutePath()); + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setTransformerRegistry(ContentTransformerRegistry transformerRegistry) + { + this.transformerRegistry = transformerRegistry; + } + + public void setStore(ContentStore store) + { + this.store = store; + } + + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + public void setAvmService(AVMService service) + { + this.avmService = service; + } + + public void setImageMagickContentTransformer(ImageMagickContentTransformer imageMagickContentTransformer) + { + this.imageMagickContentTransformer = imageMagickContentTransformer; + } + + /** + * Service initialise + */ + public void init() + { + // Bind on update properties behaviour + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"), + this, + new JavaBehaviour(this, "onUpdateProperties")); + + // Register on content update policy + this.onContentUpdateDelegate = this.policyComponent.registerClassPolicy(OnContentUpdatePolicy.class); + this.onContentReadDelegate = this.policyComponent.registerClassPolicy(OnContentReadPolicy.class); + } + + /** + * Update properties policy behaviour + * + * @param nodeRef the node reference + * @param before the before values of the properties + * @param after the after values of the properties + */ + public void onUpdateProperties( + NodeRef nodeRef, + Map before, + Map after) + { + boolean fire = false; + boolean newContent = false; + // check if any of the content properties have changed + for (QName propertyQName : after.keySet()) + { + // is this a content property? + PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); + if (propertyDef == null) + { + // the property is not recognised + continue; + } + if (!propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT)) + { + // not a content type + continue; + } + + try + { + ContentData beforeValue = (ContentData) before.get(propertyQName); + ContentData afterValue = (ContentData) after.get(propertyQName); + if (afterValue != null && afterValue.getContentUrl() == null) + { + // no URL - ignore + } + else if (!EqualsHelper.nullSafeEquals(beforeValue, afterValue)) + { + // So debug ... + if (logger.isDebugEnabled() == true) + { + String beforeString = ""; + if (beforeValue != null) + { + beforeString = beforeValue.toString(); + } + String afterString = ""; + if (afterValue != null) + { + afterString = afterValue.toString(); + } + logger.debug("onContentUpate: before = " + beforeString + "; after = " + afterString); + } + + // Figure out if the content is new or not + String beforeContentUrl = null; + if (beforeValue != null) + { + beforeContentUrl = beforeValue.getContentUrl(); + } + String afterContentUrl = null; + if (afterValue != null) + { + afterContentUrl = afterValue.getContentUrl(); + } + if (beforeContentUrl == null && afterContentUrl != null) + { + newContent = true; + } + + // the content changed + // at the moment, we are only interested in this one change + fire = true; + break; + } + } + catch (ClassCastException e) + { + // properties don't conform to model + continue; + } + } + // fire? + if (fire) + { + // Fire the content update policy + Set types = new HashSet(this.nodeService.getAspects(nodeRef)); + types.add(this.nodeService.getType(nodeRef)); + OnContentUpdatePolicy policy = this.onContentUpdateDelegate.get(nodeRef, types); + policy.onContentUpdate(nodeRef, newContent); + } + } + + public ContentReader getReader(NodeRef nodeRef, QName propertyQName) + { + return getReader(nodeRef, propertyQName, true); + } + + private ContentReader getReader(NodeRef nodeRef, QName propertyQName, boolean fireContentReadPolicy) + { + ContentData contentData = null; + Serializable propValue = nodeService.getProperty(nodeRef, propertyQName); + if (propValue instanceof Collection) + { + Collection colPropValue = (Collection)propValue; + if (colPropValue.size() > 0) + { + propValue = (Serializable)colPropValue.iterator().next(); + } + } + + if (propValue instanceof ContentData) + { + contentData = (ContentData)propValue; + } + + if (contentData == null) + { + // if no value or a value other content, and a property definition has been provided, ensure that it's CONTENT or ANY + PropertyDefinition contentPropDef = dictionaryService.getProperty(propertyQName); + if (contentPropDef != null && + (!(contentPropDef.getDataType().getName().equals(DataTypeDefinition.CONTENT) || + contentPropDef.getDataType().getName().equals(DataTypeDefinition.ANY)))) + { + throw new InvalidTypeException("The node property must be of type content: \n" + + " node: " + nodeRef + "\n" + + " property name: " + propertyQName + "\n" + + " property type: " + ((contentPropDef == null) ? "unknown" : contentPropDef.getDataType()), + propertyQName); + } + } + + // check that the URL is available + if (contentData == null || contentData.getContentUrl() == null) + { + // there is no URL - the interface specifies that this is not an error condition + return null; + } + String contentUrl = contentData.getContentUrl(); + + // TODO: Choose the store to read from at runtime + ContentReader reader = store.getReader(contentUrl); + + // set extra data on the reader + reader.setMimetype(contentData.getMimetype()); + reader.setEncoding(contentData.getEncoding()); + + // Fire the content read policy + if (reader != null && fireContentReadPolicy == true) + { + // Fire the content update policy + Set types = new HashSet(this.nodeService.getAspects(nodeRef)); + types.add(this.nodeService.getType(nodeRef)); + OnContentReadPolicy policy = this.onContentReadDelegate.get(nodeRef, types); + policy.onContentRead(nodeRef); + } + + // we don't listen for anything + // result may be null - but interface contract says we may return null + return reader; + } + + public ContentWriter getWriter(NodeRef nodeRef, QName propertyQName, boolean update) + { + // check for an existing URL - the get of the reader will perform type checking + ContentReader existingContentReader = getReader(nodeRef, propertyQName, false); + + // TODO: Choose the store to write to at runtime + + // get the content using the (potentially) existing content - the new content + // can be wherever the store decides. + ContentWriter writer = store.getWriter(existingContentReader, null); + + // Special case for AVM repository. + Serializable contentValue = null; + if (nodeRef.getStoreRef().getProtocol().equals(StoreRef.PROTOCOL_AVM)) + { + Pair avmVersionPath = AVMNodeConverter.ToAVMVersionPath(nodeRef); + contentValue = avmService.getContentDataForWrite(avmVersionPath.getSecond()); + } + else + { + contentValue = nodeService.getProperty(nodeRef, propertyQName); + } + + // set extra data on the reader if the property is pre-existing + if (contentValue != null && contentValue instanceof ContentData) + { + ContentData contentData = (ContentData)contentValue; + writer.setMimetype(contentData.getMimetype()); + writer.setEncoding(contentData.getEncoding()); + } + + // attach a listener if required + if (update) + { + // need a listener to update the node when the stream closes + WriteStreamListener listener = new WriteStreamListener(nodeService, nodeRef, propertyQName, writer); + writer.addListener(listener); + writer.setTransactionService(transactionService); + } + + // give back to the client + return writer; + } + + /** + * @return Returns a writer to an anonymous location + */ + public ContentWriter getTempWriter() + { + // there is no existing content and we don't specify the location of the new content + return tempStore.getWriter(null, null); + } + + /** + * @see org.alfresco.repo.content.transform.ContentTransformerRegistry + * @see org.alfresco.repo.content.transform.ContentTransformer + */ + public void transform(ContentReader reader, ContentWriter writer) + throws NoTransformerException, ContentIOException + { + // check that source and target mimetypes are available + String sourceMimetype = reader.getMimetype(); + if (sourceMimetype == null) + { + throw new AlfrescoRuntimeException("The content reader mimetype must be set: " + reader); + } + String targetMimetype = writer.getMimetype(); + if (targetMimetype == null) + { + throw new AlfrescoRuntimeException("The content writer mimetype must be set: " + writer); + } + // look for a transformer + ContentTransformer transformer = transformerRegistry.getTransformer(sourceMimetype, targetMimetype); + if (transformer == null) + { + throw new NoTransformerException(sourceMimetype, targetMimetype); + } + // we have a transformer, so do it + transformer.transform(reader, writer); + // done + } + + /** + * @see org.alfresco.repo.content.transform.ContentTransformerRegistry + * @see org.alfresco.repo.content.transform.ContentTransformer + */ + public ContentTransformer getTransformer(String sourceMimetype, String targetMimetype) + { + // look for a transformer + ContentTransformer transformer = transformerRegistry.getTransformer(sourceMimetype, targetMimetype); + // done + return transformer; + } + + /** + * @see org.alfresco.service.cmr.repository.ContentService#getImageTransformer() + */ + public ContentTransformer getImageTransformer() + { + return imageMagickContentTransformer; + } + + /** + * @see org.alfresco.repo.content.transform.ContentTransformerRegistry + * @see org.alfresco.repo.content.transform.ContentTransformer + */ + public boolean isTransformable(ContentReader reader, ContentWriter writer) + { + // check that source and target mimetypes are available + String sourceMimetype = reader.getMimetype(); + if (sourceMimetype == null) + { + throw new AlfrescoRuntimeException("The content reader mimetype must be set: " + reader); + } + String targetMimetype = writer.getMimetype(); + if (targetMimetype == null) + { + throw new AlfrescoRuntimeException("The content writer mimetype must be set: " + writer); + } + + // look for a transformer + ContentTransformer transformer = transformerRegistry.getTransformer(sourceMimetype, targetMimetype); + return (transformer != null); + } + + /** + * Ensures that, upon closure of the output stream, the node is updated with + * the latest URL of the content to which it refers. + *

    + * The listener close operation does not need a transaction as the + * ContentWriter takes care of that. + * + * @author Derek Hulley + */ + private static class WriteStreamListener implements ContentStreamListener + { + private NodeService nodeService; + private NodeRef nodeRef; + private QName propertyQName; + private ContentWriter writer; + + public WriteStreamListener( + NodeService nodeService, + NodeRef nodeRef, + QName propertyQName, + ContentWriter writer) + { + this.nodeService = nodeService; + this.nodeRef = nodeRef; + this.propertyQName = propertyQName; + this.writer = writer; + } + + public void contentStreamClosed() throws ContentIOException + { + try + { + // set the full content property + ContentData contentData = writer.getContentData(); + nodeService.setProperty( + nodeRef, + propertyQName, + contentData); + // done + if (logger.isDebugEnabled()) + { + logger.debug("Stream listener updated node: \n" + + " node: " + nodeRef + "\n" + + " property: " + propertyQName + "\n" + + " value: " + contentData); + } + } + catch (Throwable e) + { + throw new ContentIOException("Failed to set content property on stream closure: \n" + + " node: " + nodeRef + "\n" + + " property: " + propertyQName + "\n" + + " writer: " + writer, + e); + } + } + } +} diff --git a/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleaner.java b/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleaner.java index 33ebf84479..221ac2b0d9 100644 --- a/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleaner.java +++ b/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleaner.java @@ -1,249 +1,249 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.repo.content.cleanup; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.avm.AVMNodeDAO; -import org.alfresco.repo.content.ContentStore; -import org.alfresco.repo.node.db.NodeDaoService; -import org.alfresco.repo.transaction.TransactionUtil; -import org.alfresco.repo.transaction.TransactionUtil.TransactionWork; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.PropertyCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * This component is responsible for finding orphaned content in a given - * content store or stores. Deletion handlers can be provided to ensure - * that the content is moved to another location prior to being removed - * from the store(s) being cleaned. - * - * @author Derek Hulley - */ -public class ContentStoreCleaner -{ - private static Log logger = LogFactory.getLog(ContentStoreCleaner.class); - - private DictionaryService dictionaryService; - private NodeDaoService nodeDaoService; - private TransactionService transactionService; - private AVMNodeDAO avmNodeDAO; - private List stores; - private List listeners; - private int protectDays; - - public ContentStoreCleaner() - { - this.stores = new ArrayList(0); - this.listeners = new ArrayList(0); - this.protectDays = 7; - } - - /** - * @param dictionaryService used to determine which properties are content properties - */ - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - /** - * @param nodeDaoService used to get the property values - */ - public void setNodeDaoService(NodeDaoService nodeDaoService) - { - this.nodeDaoService = nodeDaoService; - } - - /** - * Setter for Spring. - * @param avmNodeDAO The AVM Node DAO to get urls with. - */ - public void setAvmNodeDAO(AVMNodeDAO avmNodeDAO) - { - this.avmNodeDAO = avmNodeDAO; - } - - /** - * @param transactionService the component to ensure proper transactional wrapping - */ - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - /** - * @param stores the content stores to clean - */ - public void setStores(List stores) - { - this.stores = stores; - } - - /** - * @param listeners the listeners that can react to deletions - */ - public void setListeners(List listeners) - { - this.listeners = listeners; - } - - /** - * Set the minimum number of days old that orphaned content must be - * before deletion is possible. The default is 7 days. - * - * @param protectDays minimum age (in days) of deleted content - */ - public void setProtectDays(int protectDays) - { - this.protectDays = protectDays; - } - - /** - * Perform basic checks to ensure that the necessary dependencies were injected. - */ - private void checkProperties() - { - PropertyCheck.mandatory(this, "dictionaryService", dictionaryService); - PropertyCheck.mandatory(this, "nodeDaoService", nodeDaoService); - PropertyCheck.mandatory(this, "transactionService", transactionService); - PropertyCheck.mandatory(this, "listeners", listeners); - - // check the protect days - if (protectDays < 0) - { - throw new AlfrescoRuntimeException("Property 'protectDays' must be 0 or greater (0 is not recommended)"); - } - else if (protectDays == 0) - { - logger.warn( - "Property 'protectDays' is set to 0. " + - "It is possible that in-transaction content will be deleted."); - } - } - - private Set getValidUrls() - { - final DataTypeDefinition contentDataType = dictionaryService.getDataType(DataTypeDefinition.CONTENT); - // wrap to make the request in a transaction - TransactionWork> getUrlsWork = new TransactionWork>() - { - public List doWork() throws Exception - { - return nodeDaoService.getPropertyValuesByActualType(contentDataType); - }; - }; - // execute in READ-ONLY txn - List values = TransactionUtil.executeInUserTransaction( - transactionService, - getUrlsWork, - true); - - // Do the same for the AVM repository. - TransactionWork> getAVMUrlsWork = new TransactionWork>() - { - public List doWork() throws Exception - { - return avmNodeDAO.getContentUrls(); - } - }; - - List avmContentUrls = TransactionUtil.executeInUserTransaction( - transactionService, - getAVMUrlsWork, - true); - - // get all valid URLs - Set validUrls = new HashSet(values.size()); - // convert the strings to objects and extract the URL - for (Serializable value : values) - { - ContentData contentData = (ContentData) value; - if (contentData.getContentUrl() != null) - { - // a URL was present - validUrls.add(contentData.getContentUrl()); - } - } - // put all the avm urls into validUrls. - for (String url : avmContentUrls) - { - validUrls.add(url); - } - - // done - if (logger.isDebugEnabled()) - { - logger.debug("Found " + validUrls.size() + " valid URLs in metadata"); - } - return validUrls; - } - - public void execute() - { - checkProperties(); - Set validUrls = getValidUrls(); - // now clean each store in turn - for (ContentStore store : stores) - { - clean(validUrls, store); - } - } - - private void clean(Set validUrls, ContentStore store) - { - Date checkAllBeforeDate = new Date(System.currentTimeMillis() - (long) protectDays * 3600L * 1000L * 24L); - // get the store's URLs - Set storeUrls = store.getUrls(null, checkAllBeforeDate); - // remove all URLs that occur in the validUrls - storeUrls.removeAll(validUrls); - // now clean the store - for (String url : storeUrls) - { - ContentReader sourceReader = store.getReader(url); - // announce this to the listeners - for (ContentStoreCleanerListener listener : listeners) - { - // get a fresh reader - ContentReader listenerReader = sourceReader.getReader(); - // call it - listener.beforeDelete(listenerReader); - } - // delete it - store.delete(url); - - if (logger.isDebugEnabled()) - { - logger.debug("Removed URL from store: \n" + - " Store: " + store + "\n" + - " URL: " + url); - } - } - } -} +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.content.cleanup; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.avm.AVMNodeDAO; +import org.alfresco.repo.content.ContentStore; +import org.alfresco.repo.node.db.NodeDaoService; +import org.alfresco.repo.transaction.TransactionUtil; +import org.alfresco.repo.transaction.TransactionUtil.TransactionWork; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This component is responsible for finding orphaned content in a given + * content store or stores. Deletion handlers can be provided to ensure + * that the content is moved to another location prior to being removed + * from the store(s) being cleaned. + * + * @author Derek Hulley + */ +public class ContentStoreCleaner +{ + private static Log logger = LogFactory.getLog(ContentStoreCleaner.class); + + private DictionaryService dictionaryService; + private NodeDaoService nodeDaoService; + private TransactionService transactionService; + private AVMNodeDAO avmNodeDAO; + private List stores; + private List listeners; + private int protectDays; + + public ContentStoreCleaner() + { + this.stores = new ArrayList(0); + this.listeners = new ArrayList(0); + this.protectDays = 7; + } + + /** + * @param dictionaryService used to determine which properties are content properties + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * @param nodeDaoService used to get the property values + */ + public void setNodeDaoService(NodeDaoService nodeDaoService) + { + this.nodeDaoService = nodeDaoService; + } + + /** + * Setter for Spring. + * @param avmNodeDAO The AVM Node DAO to get urls with. + */ + public void setAvmNodeDAO(AVMNodeDAO avmNodeDAO) + { + this.avmNodeDAO = avmNodeDAO; + } + + /** + * @param transactionService the component to ensure proper transactional wrapping + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + /** + * @param stores the content stores to clean + */ + public void setStores(List stores) + { + this.stores = stores; + } + + /** + * @param listeners the listeners that can react to deletions + */ + public void setListeners(List listeners) + { + this.listeners = listeners; + } + + /** + * Set the minimum number of days old that orphaned content must be + * before deletion is possible. The default is 7 days. + * + * @param protectDays minimum age (in days) of deleted content + */ + public void setProtectDays(int protectDays) + { + this.protectDays = protectDays; + } + + /** + * Perform basic checks to ensure that the necessary dependencies were injected. + */ + private void checkProperties() + { + PropertyCheck.mandatory(this, "dictionaryService", dictionaryService); + PropertyCheck.mandatory(this, "nodeDaoService", nodeDaoService); + PropertyCheck.mandatory(this, "transactionService", transactionService); + PropertyCheck.mandatory(this, "listeners", listeners); + + // check the protect days + if (protectDays < 0) + { + throw new AlfrescoRuntimeException("Property 'protectDays' must be 0 or greater (0 is not recommended)"); + } + else if (protectDays == 0) + { + logger.warn( + "Property 'protectDays' is set to 0. " + + "It is possible that in-transaction content will be deleted."); + } + } + + private Set getValidUrls() + { + final DataTypeDefinition contentDataType = dictionaryService.getDataType(DataTypeDefinition.CONTENT); + // wrap to make the request in a transaction + TransactionWork> getUrlsWork = new TransactionWork>() + { + public List doWork() throws Exception + { + return nodeDaoService.getPropertyValuesByActualType(contentDataType); + }; + }; + // execute in READ-ONLY txn + List values = TransactionUtil.executeInUserTransaction( + transactionService, + getUrlsWork, + true); + + // Do the same for the AVM repository. + TransactionWork> getAVMUrlsWork = new TransactionWork>() + { + public List doWork() throws Exception + { + return avmNodeDAO.getContentUrls(); + } + }; + + List avmContentUrls = TransactionUtil.executeInUserTransaction( + transactionService, + getAVMUrlsWork, + true); + + // get all valid URLs + Set validUrls = new HashSet(values.size()); + // convert the strings to objects and extract the URL + for (Serializable value : values) + { + ContentData contentData = (ContentData) value; + if (contentData.getContentUrl() != null) + { + // a URL was present + validUrls.add(contentData.getContentUrl()); + } + } + // put all the avm urls into validUrls. + for (String url : avmContentUrls) + { + validUrls.add(url); + } + + // done + if (logger.isDebugEnabled()) + { + logger.debug("Found " + validUrls.size() + " valid URLs in metadata"); + } + return validUrls; + } + + public void execute() + { + checkProperties(); + Set validUrls = getValidUrls(); + // now clean each store in turn + for (ContentStore store : stores) + { + clean(validUrls, store); + } + } + + private void clean(Set validUrls, ContentStore store) + { + Date checkAllBeforeDate = new Date(System.currentTimeMillis() - (long) protectDays * 3600L * 1000L * 24L); + // get the store's URLs + Set storeUrls = store.getUrls(null, checkAllBeforeDate); + // remove all URLs that occur in the validUrls + storeUrls.removeAll(validUrls); + // now clean the store + for (String url : storeUrls) + { + ContentReader sourceReader = store.getReader(url); + // announce this to the listeners + for (ContentStoreCleanerListener listener : listeners) + { + // get a fresh reader + ContentReader listenerReader = sourceReader.getReader(); + // call it + listener.beforeDelete(listenerReader); + } + // delete it + store.delete(url); + + if (logger.isDebugEnabled()) + { + logger.debug("Removed URL from store: \n" + + " Store: " + store + "\n" + + " URL: " + url); + } + } + } +} diff --git a/source/java/org/alfresco/repo/copy/CopyServiceImpl.java b/source/java/org/alfresco/repo/copy/CopyServiceImpl.java index d9a0f7e59e..34e5436456 100644 --- a/source/java/org/alfresco/repo/copy/CopyServiceImpl.java +++ b/source/java/org/alfresco/repo/copy/CopyServiceImpl.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.i18n.I18NUtil; import org.alfresco.model.ContentModel; import org.alfresco.repo.policy.ClassPolicyDelegate; import org.alfresco.repo.policy.JavaBehaviour; @@ -43,6 +44,7 @@ import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.CopyService; import org.alfresco.service.cmr.repository.CopyServiceException; +import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; @@ -65,8 +67,12 @@ import org.alfresco.util.ParameterCheck; */ public class CopyServiceImpl implements CopyService { + /** I18N labels */ + private String COPY_OF_LABEL = "copy_service.copy_of_label"; + /** The node service */ private NodeService nodeService; + private NodeService internalNodeService; /** The dictionary service*/ private DictionaryService dictionaryService; @@ -99,6 +105,16 @@ public class CopyServiceImpl implements CopyService { this.nodeService = nodeService; } + + /** + * Sets the internal node service + * + * @param internalNodeService the internal node service + */ + public void setInternalNodeService(NodeService internalNodeService) + { + this.internalNodeService = internalNodeService; + } /** * Sets the dictionary service @@ -233,7 +249,32 @@ public class CopyServiceImpl implements CopyService return copy; } - + + public NodeRef copyAndRename(NodeRef sourceNodeRef, NodeRef destinationParent, QName destinationAssocTypeQName, QName destinationQName, boolean copyChildren) + { + // Make a note of the source name and do the copy + String sourceName = (String)this.internalNodeService.getProperty(sourceNodeRef, ContentModel.PROP_NAME); + NodeRef copy = copy(sourceNodeRef, destinationParent, destinationAssocTypeQName, destinationQName, copyChildren); + + // Do the rename, iterating until a non-duplicate name is found + boolean bDone = false; + while (bDone == false) + { + try + { + this.internalNodeService.setProperty(copy, ContentModel.PROP_NAME, sourceName); + bDone = true; + } + catch(DuplicateChildNodeNameException exception) + { + sourceName = I18NUtil.getMessage(COPY_OF_LABEL, sourceName); + } + } + + // Return the copy + return copy; + } + /** * Invokes the copy complete policy for the node reference provided * diff --git a/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java b/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java index 973ff0c827..426d157706 100644 --- a/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java +++ b/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java @@ -104,6 +104,7 @@ public class CopyServiceImplTest extends BaseSpringTest private static final QName TEST_MANDATORY_ASPECT_QNAME = QName.createQName(TEST_TYPE_NAMESPACE, "testMandatoryAspect"); private static final QName PROP5_QNAME_MANDATORY = QName.createQName(TEST_TYPE_NAMESPACE, "prop5Mandatory"); + private static final String TEST_NAME = "testName"; private static final String TEST_VALUE_1 = "testValue1"; private static final String TEST_VALUE_2 = "testValue2"; private static final String TEST_VALUE_3 = "testValue3"; @@ -239,6 +240,7 @@ public class CopyServiceImplTest extends BaseSpringTest private Map createTypePropertyBag() { Map result = new HashMap(); + result.put(ContentModel.PROP_NAME, TEST_NAME); result.put(PROP1_QNAME_MANDATORY, TEST_VALUE_1); result.put(PROP2_QNAME_OPTIONAL, TEST_VALUE_2); result.put(PROP5_QNAME_MANDATORY, TEST_VALUE_3); @@ -624,6 +626,31 @@ public class CopyServiceImplTest extends BaseSpringTest assertNotNull(value); assertEquals(nodeTwoCopy, value); } + + public void testCopyAndRename() + { + // Check a normal copy with no dup restrictions + NodeRef copy = this.copyService.copyAndRename( + this.sourceNodeRef, + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}copyAssoc"), + false); + checkCopiedNode(this.sourceNodeRef, copy, true, true, false); + assertTrue(TEST_NAME.equals(this.nodeService.getProperty(copy, ContentModel.PROP_NAME))); + + // Create a folder and content node + Map propsFolder = new HashMap(1); + propsFolder.put(ContentModel.PROP_NAME, "tempFolder"); + NodeRef folderNode = this.nodeService.createNode(this.rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}tempFolder"), ContentModel.TYPE_FOLDER, propsFolder).getChildRef(); + Map props = new HashMap(1); + props.put(ContentModel.PROP_NAME, TEST_NAME); + NodeRef contentNode = this.nodeService.createNode(folderNode, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}renametest"), ContentModel.TYPE_CONTENT, props).getChildRef(); + + // Now copy the content node with the duplicate name restriction + NodeRef contentCopy = this.copyService.copy(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}bobbins"), false); + assertFalse(TEST_NAME.equals(this.nodeService.getProperty(contentCopy, ContentModel.PROP_NAME))); + } /** * Check that the copied node contains the state we are expecting diff --git a/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java b/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java index 158b3fae5b..fc813f1bc4 100644 --- a/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java +++ b/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java @@ -41,16 +41,12 @@ import org.alfresco.service.license.LicenseService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.AbstractLifecycleBean; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.core.io.Resource; @@ -59,12 +55,10 @@ import org.springframework.core.io.Resource; * * @author David Caruana */ -public class DescriptorServiceImpl implements DescriptorService, ApplicationListener, InitializingBean, ApplicationContextAware, DisposableBean +public class DescriptorServiceImpl extends AbstractLifecycleBean implements DescriptorService, InitializingBean { private static Log logger = LogFactory.getLog(DescriptorServiceImpl.class); - private ApplicationContext applicationContext; - private Properties serverProperties; private ImporterBootstrap systemBootstrap; @@ -78,14 +72,6 @@ public class DescriptorServiceImpl implements DescriptorService, ApplicationList private Descriptor installedRepoDescriptor; - /* (non-Javadoc) - * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) - */ - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException - { - this.applicationContext = applicationContext; - } - /** * Sets the server descriptor from a resource file * @@ -163,36 +149,36 @@ public class DescriptorServiceImpl implements DescriptorService, ApplicationList return (licenseService == null) ? null : licenseService.getLicense(); } - /** - * @param event - */ - public void onApplicationEvent(ApplicationEvent event) + @Override + protected void onBootstrap(ApplicationEvent event) { - if (event instanceof ContextRefreshedEvent) + // initialise the repository descriptor + // note: this requires that the repository schema has already been initialised + TransactionWork createDescriptorWork = new TransactionUtil.TransactionWork() { - // initialise the repository descriptor - // note: this requires that the repository schema has already been initialised - TransactionWork createDescriptorWork = new TransactionUtil.TransactionWork() + public Descriptor doWork() { - public Descriptor doWork() - { - // initialise license service (if installed) - initialiseLicenseService(); - - // verify license, but only if license component is installed - licenseService.verifyLicense(); - - // persist the server descriptor values - updateCurrentRepositoryDescriptor(serverDescriptor); + // initialise license service (if installed) + initialiseLicenseService(); + + // verify license, but only if license component is installed + licenseService.verifyLicense(); + + // persist the server descriptor values + updateCurrentRepositoryDescriptor(serverDescriptor); - // return the repository installed descriptor - return createInstalledRepositoryDescriptor(); - } - }; - installedRepoDescriptor = TransactionUtil.executeInUserTransaction(transactionService, createDescriptorWork); - } + // return the repository installed descriptor + return createInstalledRepositoryDescriptor(); + } + }; + installedRepoDescriptor = TransactionUtil.executeInUserTransaction(transactionService, createDescriptorWork); } + @Override + protected void onShutdown(ApplicationEvent event) + { + } + /** * Initialise Descriptors */ @@ -202,13 +188,6 @@ public class DescriptorServiceImpl implements DescriptorService, ApplicationList serverDescriptor = createServerDescriptor(); } - /** - * Destruction hook - */ - public void destroy() throws Exception - { - } - /** * Create server descriptor * @@ -358,7 +337,7 @@ public class DescriptorServiceImpl implements DescriptorService, ApplicationList // be declaratively taken out in an installed environment. Class licenseComponentClass = Class.forName("org.alfresco.license.LicenseComponent"); Constructor constructor = licenseComponentClass.getConstructor(new Class[] { ApplicationContext.class} ); - licenseService = (LicenseService)constructor.newInstance(new Object[] { applicationContext }); + licenseService = (LicenseService)constructor.newInstance(new Object[] { getApplicationContext() }); } catch (ClassNotFoundException e) { @@ -766,4 +745,5 @@ public class DescriptorServiceImpl implements DescriptorService, ApplicationList return serverProperties.getProperty(key, ""); } } + } diff --git a/source/java/org/alfresco/repo/descriptor/DescriptorStartupLog.java b/source/java/org/alfresco/repo/descriptor/DescriptorStartupLog.java index 13d77fff3b..bd802737cc 100644 --- a/source/java/org/alfresco/repo/descriptor/DescriptorStartupLog.java +++ b/source/java/org/alfresco/repo/descriptor/DescriptorStartupLog.java @@ -23,11 +23,10 @@ import java.util.Map; import org.alfresco.service.descriptor.Descriptor; import org.alfresco.service.descriptor.DescriptorService; import org.alfresco.service.license.LicenseDescriptor; +import org.alfresco.util.AbstractLifecycleBean; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.ContextRefreshedEvent; /** @@ -35,7 +34,7 @@ import org.springframework.context.event.ContextRefreshedEvent; * * @author davidc */ -public class DescriptorStartupLog implements ApplicationListener +public class DescriptorStartupLog extends AbstractLifecycleBean { // Logger private static final Log logger = LogFactory.getLog(DescriptorService.class); @@ -52,82 +51,6 @@ public class DescriptorStartupLog implements ApplicationListener } - /** - * @param event - */ - public void onApplicationEvent(ApplicationEvent event) - { - if (event instanceof ContextRefreshedEvent) - { - // - // log output of VM stats - // - Map properties = System.getProperties(); - String version = (properties.get("java.runtime.version") == null) ? "unknown" : (String)properties.get("java.runtime.version"); - long maxHeap = Runtime.getRuntime().maxMemory(); - float maxHeapMB = maxHeap / 1024l; - maxHeapMB = maxHeapMB / 1024l; - if (logger.isInfoEnabled()) - { - logger.info(String.format("Alfresco JVM - v%s; maximum heap size %.3fMB", version, maxHeapMB)); - } - if (logger.isWarnEnabled()) - { - if (version.startsWith("1.2") || version.startsWith("1.3") || version.startsWith("1.4")) - { - logger.warn(String.format("Alfresco JVM - WARNING - v1.5 is required; currently using v%s", version)); - } - if (maxHeapMB < 500) - { - logger.warn(String.format("Alfresco JVM - WARNING - maximum heap size %.3fMB is less than recommended 512MB", maxHeapMB)); - } - } - - // Log License Descriptors (if applicable) - LicenseDescriptor license = descriptorService.getLicenseDescriptor(); - if (license != null && logger.isInfoEnabled()) - { - String subject = license.getSubject(); - String msg = "Alfresco license: " + subject; - String holder = getHolderOrganisation(license.getHolder()); - if (holder != null) - { - msg += " granted to " + holder; - } - Date validUntil = license.getValidUntil(); - if (validUntil != null) - { - Integer days = license.getDays(); - Integer remainingDays = license.getRemainingDays(); - - msg += " limited to " + days + " days expiring " + validUntil + " (" + remainingDays + " days remaining)"; - } - else - { - msg += " (does not expire)"; - } - - - logger.info(msg); - } - - // Log Repository Descriptors - if (logger.isInfoEnabled()) - { - Descriptor serverDescriptor = descriptorService.getServerDescriptor(); - Descriptor installedRepoDescriptor = descriptorService.getInstalledRepositoryDescriptor(); - String serverEdition = serverDescriptor.getEdition(); - String serverVersion = serverDescriptor.getVersion(); - int serverSchemaVersion = serverDescriptor.getSchema(); - String installedRepoVersion = installedRepoDescriptor.getVersion(); - int installedSchemaVersion = installedRepoDescriptor.getSchema(); - logger.info(String.format("Alfresco started (%s): Current version %s schema %d - Installed version %s schema %d", - serverEdition, serverVersion, serverSchemaVersion, installedRepoVersion, installedSchemaVersion)); - } - } - } - - /** * Get Organisation from Principal * @@ -156,5 +79,83 @@ public class DescriptorStartupLog implements ApplicationListener return holder; } + + + @Override + protected void onBootstrap(ApplicationEvent event) + { + // + // log output of VM stats + // + Map properties = System.getProperties(); + String version = (properties.get("java.runtime.version") == null) ? "unknown" : (String)properties.get("java.runtime.version"); + long maxHeap = Runtime.getRuntime().maxMemory(); + float maxHeapMB = maxHeap / 1024l; + maxHeapMB = maxHeapMB / 1024l; + if (logger.isInfoEnabled()) + { + logger.info(String.format("Alfresco JVM - v%s; maximum heap size %.3fMB", version, maxHeapMB)); + } + if (logger.isWarnEnabled()) + { + if (version.startsWith("1.2") || version.startsWith("1.3") || version.startsWith("1.4")) + { + logger.warn(String.format("Alfresco JVM - WARNING - v1.5 is required; currently using v%s", version)); + } + if (maxHeapMB < 500) + { + logger.warn(String.format("Alfresco JVM - WARNING - maximum heap size %.3fMB is less than recommended 512MB", maxHeapMB)); + } + } + + // Log License Descriptors (if applicable) + LicenseDescriptor license = descriptorService.getLicenseDescriptor(); + if (license != null && logger.isInfoEnabled()) + { + String subject = license.getSubject(); + String msg = "Alfresco license: " + subject; + String holder = getHolderOrganisation(license.getHolder()); + if (holder != null) + { + msg += " granted to " + holder; + } + Date validUntil = license.getValidUntil(); + if (validUntil != null) + { + Integer days = license.getDays(); + Integer remainingDays = license.getRemainingDays(); + + msg += " limited to " + days + " days expiring " + validUntil + " (" + remainingDays + " days remaining)"; + } + else + { + msg += " (does not expire)"; + } + + + logger.info(msg); + } + + // Log Repository Descriptors + if (logger.isInfoEnabled()) + { + Descriptor serverDescriptor = descriptorService.getServerDescriptor(); + Descriptor installedRepoDescriptor = descriptorService.getInstalledRepositoryDescriptor(); + String serverEdition = serverDescriptor.getEdition(); + String serverVersion = serverDescriptor.getVersion(); + int serverSchemaVersion = serverDescriptor.getSchema(); + String installedRepoVersion = installedRepoDescriptor.getVersion(); + int installedSchemaVersion = installedRepoDescriptor.getSchema(); + logger.info(String.format("Alfresco started (%s): Current version %s schema %d - Installed version %s schema %d", + serverEdition, serverVersion, serverSchemaVersion, installedRepoVersion, installedSchemaVersion)); + } + } + + + @Override + protected void onShutdown(ApplicationEvent event) + { + // NOOP + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java b/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java index 4b5430ac50..a1e4d7da6f 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryDAOTest.java @@ -16,6 +16,7 @@ */ package org.alfresco.repo.dictionary; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -274,5 +275,26 @@ public class DictionaryDAOTest extends TestCase assertFalse(test5); } - + + public void testPropertyOverride() + { + TypeDefinition type1 = service.getType(QName.createQName(TEST_URL, "overridetype1")); + Map props1 = type1.getProperties(); + PropertyDefinition prop1 = props1.get(QName.createQName(TEST_URL, "propoverride")); + String def1 = prop1.getDefaultValue(); + assertEquals("one", def1); + + TypeDefinition type2 = service.getType(QName.createQName(TEST_URL, "overridetype2")); + Map props2 = type2.getProperties(); + PropertyDefinition prop2 = props2.get(QName.createQName(TEST_URL, "propoverride")); + String def2 = prop2.getDefaultValue(); + assertEquals("two", def2); + + TypeDefinition type3 = service.getType(QName.createQName(TEST_URL, "overridetype3")); + Map props3 = type3.getProperties(); + PropertyDefinition prop3 = props3.get(QName.createQName(TEST_URL, "propoverride")); + String def3 = prop3.getDefaultValue(); + assertEquals("three", def3); + } + } diff --git a/source/java/org/alfresco/repo/dictionary/TestModel.java b/source/java/org/alfresco/repo/dictionary/TestModel.java index d2bcb4be54..240be4ce7b 100644 --- a/source/java/org/alfresco/repo/dictionary/TestModel.java +++ b/source/java/org/alfresco/repo/dictionary/TestModel.java @@ -43,6 +43,8 @@ public class TestModel bootstrapModels.add("alfresco/model/systemModel.xml"); bootstrapModels.add("alfresco/model/contentModel.xml"); bootstrapModels.add("alfresco/model/applicationModel.xml"); + bootstrapModels.add("alfresco/model/bpmModel.xml"); + bootstrapModels.add("alfresco/workflow/workflowModel.xml"); // include models specified on command line for (String arg: args) diff --git a/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml b/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml index ba51937ef7..500c04df53 100644 --- a/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml +++ b/source/java/org/alfresco/repo/dictionary/dictionarydaotest_model.xml @@ -188,6 +188,33 @@ + + + + d:text + one + + + + + + test:overridetype1 + + + two + + + + + + test:overridetype2 + + + three + + + + diff --git a/source/java/org/alfresco/repo/domain/hibernate/ChildAssocImpl.java b/source/java/org/alfresco/repo/domain/hibernate/ChildAssocImpl.java index 4a830a42f1..679588c6ca 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/ChildAssocImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/ChildAssocImpl.java @@ -25,7 +25,6 @@ import org.alfresco.repo.domain.ChildAssoc; import org.alfresco.repo.domain.Node; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.namespace.QName; -import org.alfresco.util.EqualsHelper; /** * @author Derek Hulley @@ -124,9 +123,12 @@ public class ChildAssocImpl implements ChildAssoc, Serializable { StringBuffer sb = new StringBuffer(32); sb.append("ChildAssoc") - .append("[ parent=").append(parent) - .append(", child=").append(child) + .append("[ id=").append(id) + .append(", parent=").append(parent.getId()) + .append(", child=").append(child.getId()) + .append(", child name=").append(childNodeName) .append(", child name crc=").append(childNodeNameCrc) + .append(", assoc type=").append(getTypeQName()) .append(", assoc name=").append(getQname()) .append(", isPrimary=").append(isPrimary) .append("]"); 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 c79ac4586d..87d9900356 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/Permission.hbm.xml +++ b/source/java/org/alfresco/repo/domain/hibernate/Permission.hbm.xml @@ -144,4 +144,14 @@ ace.authority.recipient = :authorityRecipient + + select + entry + from + org.alfresco.repo.domain.hibernate.DbAccessControlEntryImpl entry + where + entry.permission.typeQname = :oldTypeQName and + entry.permission.name = :oldName + + \ No newline at end of file diff --git a/source/java/org/alfresco/repo/domain/hibernate/PermissionsDaoComponentImpl.java b/source/java/org/alfresco/repo/domain/hibernate/PermissionsDaoComponentImpl.java index 3879739aff..7e2bd5e68e 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/PermissionsDaoComponentImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/PermissionsDaoComponentImpl.java @@ -1,600 +1,600 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.repo.domain.hibernate; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import org.alfresco.repo.domain.AccessControlListDAO; -import org.alfresco.repo.domain.DbAccessControlEntry; -import org.alfresco.repo.domain.DbAccessControlList; -import org.alfresco.repo.domain.DbAuthority; -import org.alfresco.repo.domain.DbPermission; -import org.alfresco.repo.domain.DbPermissionKey; -import org.alfresco.repo.security.permissions.NodePermissionEntry; -import org.alfresco.repo.security.permissions.PermissionEntry; -import org.alfresco.repo.security.permissions.PermissionReference; -import org.alfresco.repo.security.permissions.impl.PermissionsDaoComponent; -import org.alfresco.repo.security.permissions.impl.SimpleNodePermissionEntry; -import org.alfresco.repo.security.permissions.impl.SimplePermissionEntry; -import org.alfresco.repo.security.permissions.impl.SimplePermissionReference; -import org.alfresco.repo.transaction.TransactionalDao; -import org.alfresco.service.cmr.repository.InvalidNodeRefException; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.GUID; -import org.hibernate.Query; -import org.hibernate.Session; -import org.springframework.orm.hibernate3.HibernateCallback; -import org.springframework.orm.hibernate3.support.HibernateDaoSupport; - -/** - * Support for accessing persisted permission information. - * - * This class maps between persisted objects and the external API defined in the - * PermissionsDAO interface. - * - * @author andyh - */ -public class PermissionsDaoComponentImpl extends HibernateDaoSupport implements PermissionsDaoComponent, TransactionalDao -{ - private static final boolean INHERIT_PERMISSIONS_DEFAULT = true; - public static final String QUERY_GET_PERMISSION = "permission.GetPermission"; - public static final String QUERY_GET_AC_ENTRIES_FOR_AUTHORITY = "permission.GetAccessControlEntriesForAuthority"; - public static final String QUERY_GET_AC_ENTRIES_FOR_PERMISSION = "permission.GetAccessControlEntriesForPermission"; - - private Map fProtocolToACLDAO; - - private AccessControlListDAO fDefaultACLDAO; - - /** a uuid identifying this unique instance */ - private String uuid; - - /** - * - */ - public PermissionsDaoComponentImpl() - { - this.uuid = GUID.generate(); - } - - /** - * Checks equality by type and uuid - */ - public boolean equals(Object obj) - { - if (obj == null) - { - return false; - } - else if (!(obj instanceof PermissionsDaoComponentImpl)) - { - return false; - } - PermissionsDaoComponentImpl that = (PermissionsDaoComponentImpl) obj; - return this.uuid.equals(that.uuid); - } - - /** - * @see #uuid - */ - public int hashCode() - { - return uuid.hashCode(); - } - - /** - * Does this Session contain any changes which must be - * synchronized with the store? - * - * @return true => changes are pending - */ - public boolean isDirty() - { - // create a callback for the task - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - return session.isDirty(); - } - }; - // execute the callback - return ((Boolean)getHibernateTemplate().execute(callback)).booleanValue(); - } - - /** - * Just flushes the session - */ - public void flush() - { - getSession().flush(); - } - - public void setProtocolToACLDAO(Map map) - { - fProtocolToACLDAO = map; - } - - public void setDefaultACLDAO(AccessControlListDAO defaultACLDAO) - { - fDefaultACLDAO = defaultACLDAO; - } - - public NodePermissionEntry getPermissions(NodeRef nodeRef) - { - // Create the object if it is not found. - // Null objects are not cached in hibernate - // If the object does not exist it will repeatedly query to check its - // non existence. - NodePermissionEntry npe = null; - DbAccessControlList acl = null; - try - { - acl = getAccessControlList(nodeRef, false); - } - catch (InvalidNodeRefException e) - { - // Do nothing. - } - if (acl == null) - { - // there isn't an access control list for the node - spoof a null one - SimpleNodePermissionEntry snpe = new SimpleNodePermissionEntry( - nodeRef, - true, - Collections. emptySet()); - npe = snpe; - } - else - { - npe = createSimpleNodePermissionEntry(nodeRef); - } - // done - if (logger.isDebugEnabled()) - { - logger.debug( - "Created access control list for node: \n" + - " node: " + nodeRef + "\n" + - " acl: " + npe); - } - return npe; - } - - /** - * Get the persisted access control list or create it if required. - * - * @param nodeRef - the node for which to create the list - * @param create - create the object if it is missing - * @return Returns the current access control list or null if not found - */ - private DbAccessControlList getAccessControlList(NodeRef nodeRef, boolean create) - { - DbAccessControlList acl = - getACLDAO(nodeRef).getAccessControlList(nodeRef); - if (acl == null && create) - { - acl = createAccessControlList(nodeRef); - } - // done - if (logger.isDebugEnabled()) - { - logger.debug("Retrieved access control list: \n" + - " node: " + nodeRef + "\n" + - " list: " + acl); - } - return acl; - } - - /** - * Creates an access control list for the node and removes the entry from - * the nullPermsionCache. - */ - private DbAccessControlList createAccessControlList(NodeRef nodeRef) - { - DbAccessControlList acl = new DbAccessControlListImpl(); - acl.setInherits(INHERIT_PERMISSIONS_DEFAULT); - getHibernateTemplate().save(acl); - - // maintain inverse - getACLDAO(nodeRef).setAccessControlList(nodeRef, acl); - - // done - if (logger.isDebugEnabled()) - { - logger.debug("Created Access Control List: \n" + - " node: " + nodeRef + "\n" + - " list: " + acl); - } - return acl; - } - - public void deletePermissions(NodeRef nodeRef) - { - DbAccessControlList acl = null; - try - { - acl = getAccessControlList(nodeRef, false); - } - catch (InvalidNodeRefException e) - { - return; - } - if (acl != null) - { - // maintain referencial integrity - getACLDAO(nodeRef).setAccessControlList(nodeRef, null); - // delete the access control list - it will cascade to the entries - getHibernateTemplate().delete(acl); - } - } - - @SuppressWarnings("unchecked") - public void deletePermissions(final String authority) - { - // get the authority - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session - .getNamedQuery(QUERY_GET_AC_ENTRIES_FOR_AUTHORITY) - .setString("authorityRecipient", authority); - return (Integer) HibernateHelper.deleteDbAccessControlEntries(session, query); - } - }; - Integer deletedCount = (Integer) getHibernateTemplate().execute(callback); - // done - if (logger.isDebugEnabled()) - { - logger.debug("Deleted " + deletedCount + " entries for authority " + authority); - } - } - - public void deletePermissions(final NodeRef nodeRef, final String authority) - { - DbAccessControlList acl = null; - try - { - acl = getACLDAO(nodeRef).getAccessControlList(nodeRef); - } - catch (InvalidNodeRefException e) - { - return; - } - int deletedCount = 0; - if (acl != null) - { - deletedCount = acl.deleteEntriesForAuthority(authority); - } - // done - if (logger.isDebugEnabled()) - { - logger.debug("Deleted " + deletedCount + "entries for criteria: \n" + - " node: " + nodeRef + "\n" + - " authority: " + authority); - } - } - - /** - * Deletes all permission entries (access control list entries) that match - * the given criteria. Note that the access control list for the node is - * not deleted. - */ - public void deletePermission(NodeRef nodeRef, String authority, PermissionReference permission) - { - DbAccessControlList acl = null; - try - { - acl = getACLDAO(nodeRef).getAccessControlList(nodeRef); - } - catch (InvalidNodeRefException e) - { - return; - } - int deletedCount = 0; - if (acl != null) - { - DbPermissionKey permissionKey = new DbPermissionKey(permission.getQName(), permission.getName()); - deletedCount = acl.deleteEntry(authority, permissionKey); - } - // done - if (logger.isDebugEnabled()) - { - logger.debug("Deleted " + deletedCount + "entries for criteria: \n" + - " node: " + nodeRef + "\n" + - " permission: " + permission + "\n" + - " authority: " + authority); - } - } - - public void setPermission(NodeRef nodeRef, String authority, PermissionReference permission, boolean allow) - { - // get the entry - DbAccessControlEntry entry = getAccessControlEntry(nodeRef, authority, permission); - if (entry == null) - { - // need to create it - DbAccessControlList dbAccessControlList = getAccessControlList(nodeRef, true); - DbPermission dbPermission = getPermission(permission, true); - DbAuthority dbAuthority = getAuthority(authority, true); - // set persistent objects - entry = dbAccessControlList.newEntry(dbPermission, dbAuthority, allow); - // done - if (logger.isDebugEnabled()) - { - logger.debug("Created new access control entry: " + entry); - } - } - else - { - entry.setAllowed(allow); - // done - if (logger.isDebugEnabled()) - { - logger.debug("Updated access control entry: " + entry); - } - } - } - - /** - * @param nodeRef the node against which to join - * @param authority the authority against which to join - * @param perm the permission against which to join - * @return Returns all access control entries that match the criteria - */ - private DbAccessControlEntry getAccessControlEntry( - NodeRef nodeRef, - String authority, - PermissionReference permission) - { - DbAccessControlList acl = getAccessControlList(nodeRef, false); - DbAccessControlEntry entry = null; - if (acl != null) - { - DbPermissionKey permissionKey = new DbPermissionKey(permission.getQName(), permission.getName()); - entry = acl.getEntry(authority, permissionKey); - } - // done - if (logger.isDebugEnabled()) - { - logger.debug("" + (entry == null ? "Did not find" : "Found") + " entry for criteria: \n" + - " node: " + nodeRef + "\n" + - " authority: " + authority + "\n" + - " permission: " + permission); - } - return entry; - } - - /** - * Utility method to find or create a persisted authority - */ - private DbAuthority getAuthority(String authority, boolean create) - { - DbAuthority entity = (DbAuthority) getHibernateTemplate().get(DbAuthorityImpl.class, authority); - if ((entity == null) && create) - { - entity = new DbAuthorityImpl(); - entity.setRecipient(authority); - getHibernateTemplate().save(entity); - return entity; - } - else - { - return entity; - } - } - - /** - * Utility method to find and optionally create a persisted permission. - */ - private DbPermission getPermission(PermissionReference permissionRef, final boolean create) - { - final QName qname = permissionRef.getQName(); - final String name = permissionRef.getName(); - Session session = getSession(); - - DbPermission dbPermission = DbPermissionImpl.find(session, qname, name); - - // create if necessary - if ((dbPermission == null) && create) - { - dbPermission = new DbPermissionImpl(); - dbPermission.setTypeQname(qname); - dbPermission.setName(name); - getHibernateTemplate().save(dbPermission); - } - return dbPermission; - } - - public void setPermission(PermissionEntry permissionEntry) - { - setPermission( - permissionEntry.getNodeRef(), - permissionEntry.getAuthority(), - permissionEntry.getPermissionReference(), - permissionEntry.isAllowed()); - } - - public void setPermission(NodePermissionEntry nodePermissionEntry) - { - NodeRef nodeRef = nodePermissionEntry.getNodeRef(); - - // Get the access control list - // Note the logic here requires to know whether it was created or not - DbAccessControlList acl = getAccessControlList(nodeRef, false); - if (acl != null) - { - // maintain referencial integrity - getACLDAO(nodeRef).setAccessControlList(nodeRef, null); - // drop the list - getHibernateTemplate().delete(acl); - } - // create the access control list - acl = createAccessControlList(nodeRef); - - // set attributes - acl.setInherits(nodePermissionEntry.inheritPermissions()); - - // add all entries - for (PermissionEntry pe : nodePermissionEntry.getPermissionEntries()) - { - PermissionReference permission = pe.getPermissionReference(); - String authority = pe.getAuthority(); - boolean isAllowed = pe.isAllowed(); - - DbPermission permissionEntity = getPermission(permission, true); - DbAuthority authorityEntity = getAuthority(authority, true); - - @SuppressWarnings("unused") - DbAccessControlEntryImpl entry = acl.newEntry(permissionEntity, authorityEntity, isAllowed); - } - } - - public void setInheritParentPermissions(NodeRef nodeRef, boolean inheritParentPermissions) - { - DbAccessControlList acl = null; - if (!inheritParentPermissions) - { - // Inheritance == true is the default, so only force a create of the ACL if the value false - acl = getAccessControlList(nodeRef, true); - acl.setInherits(false); - } - else - { - acl = getAccessControlList(nodeRef, false); - if (acl != null) - { - acl.setInherits(true); - } - } - } - - public boolean getInheritParentPermissions(NodeRef nodeRef) - { - DbAccessControlList acl = null; - try - { - acl = getAccessControlList(nodeRef, false); - } - catch (InvalidNodeRefException e) - { - return INHERIT_PERMISSIONS_DEFAULT; - } - if (acl == null) - { - return true; - } - else - { - return acl.getInherits(); - } - } - - // Utility methods to create simple detached objects for the outside world - // We do not pass out the hibernate objects - - private SimpleNodePermissionEntry createSimpleNodePermissionEntry(NodeRef nodeRef) - { - DbAccessControlList acl = - getACLDAO(nodeRef).getAccessControlList(nodeRef); - if (acl == null) - { - // there isn't an access control list for the node - spoof a null one - SimpleNodePermissionEntry snpe = new SimpleNodePermissionEntry( - nodeRef, - true, - Collections. emptySet()); - return snpe; - } - else - { - Set entries = acl.getEntries(); - SimpleNodePermissionEntry snpe = new SimpleNodePermissionEntry( - nodeRef, - acl.getInherits(), - createSimplePermissionEntries(nodeRef, entries)); - return snpe; - } - } - - /** - * @param entries access control entries - * @return Returns a unique set of entries that can be given back to the outside world - */ - private Set createSimplePermissionEntries(NodeRef nodeRef, - Collection entries) - { - if (entries == null) - { - return null; - } - HashSet spes = new HashSet(entries.size(), 1.0f); - if (entries.size() != 0) - { - for (DbAccessControlEntry entry : entries) - { - spes.add(createSimplePermissionEntry(nodeRef, entry)); - } - } - return spes; - } - - private static SimplePermissionEntry createSimplePermissionEntry(NodeRef nodeRef, - DbAccessControlEntry ace) - { - if (ace == null) - { - return null; - } - return new SimplePermissionEntry( - nodeRef, - createSimplePermissionReference(ace.getPermission()), - ace.getAuthority().getRecipient(), - ace.isAllowed() ? AccessStatus.ALLOWED : AccessStatus.DENIED); - } - - private static SimplePermissionReference createSimplePermissionReference(DbPermission perm) - { - if (perm == null) - { - return null; - } - return new SimplePermissionReference( - perm.getTypeQname(), - perm.getName()); - } - - /** - * Helper to choose appropriate NodeService for the given NodeRef - * @param nodeRef The NodeRef to dispatch from. - * @return The appropriate NodeService. - */ - private AccessControlListDAO getACLDAO(NodeRef nodeRef) - { - AccessControlListDAO ret = fProtocolToACLDAO.get(nodeRef.getStoreRef().getProtocol()); - if (ret == null) - { - return fDefaultACLDAO; - } - return ret; - } -} +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.domain.hibernate; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.alfresco.repo.domain.AccessControlListDAO; +import org.alfresco.repo.domain.DbAccessControlEntry; +import org.alfresco.repo.domain.DbAccessControlList; +import org.alfresco.repo.domain.DbAuthority; +import org.alfresco.repo.domain.DbPermission; +import org.alfresco.repo.domain.DbPermissionKey; +import org.alfresco.repo.security.permissions.NodePermissionEntry; +import org.alfresco.repo.security.permissions.PermissionEntry; +import org.alfresco.repo.security.permissions.PermissionReference; +import org.alfresco.repo.security.permissions.impl.PermissionsDaoComponent; +import org.alfresco.repo.security.permissions.impl.SimpleNodePermissionEntry; +import org.alfresco.repo.security.permissions.impl.SimplePermissionEntry; +import org.alfresco.repo.security.permissions.impl.SimplePermissionReference; +import org.alfresco.repo.transaction.TransactionalDao; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.GUID; +import org.hibernate.Query; +import org.hibernate.Session; +import org.springframework.orm.hibernate3.HibernateCallback; +import org.springframework.orm.hibernate3.support.HibernateDaoSupport; + +/** + * Support for accessing persisted permission information. + * + * This class maps between persisted objects and the external API defined in the + * PermissionsDAO interface. + * + * @author andyh + */ +public class PermissionsDaoComponentImpl extends HibernateDaoSupport implements PermissionsDaoComponent, TransactionalDao +{ + private static final boolean INHERIT_PERMISSIONS_DEFAULT = true; + public static final String QUERY_GET_PERMISSION = "permission.GetPermission"; + public static final String QUERY_GET_AC_ENTRIES_FOR_AUTHORITY = "permission.GetAccessControlEntriesForAuthority"; + public static final String QUERY_GET_AC_ENTRIES_FOR_PERMISSION = "permission.GetAccessControlEntriesForPermission"; + + private Map fProtocolToACLDAO; + + private AccessControlListDAO fDefaultACLDAO; + + /** a uuid identifying this unique instance */ + private String uuid; + + /** + * + */ + public PermissionsDaoComponentImpl() + { + this.uuid = GUID.generate(); + } + + /** + * Checks equality by type and uuid + */ + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + else if (!(obj instanceof PermissionsDaoComponentImpl)) + { + return false; + } + PermissionsDaoComponentImpl that = (PermissionsDaoComponentImpl) obj; + return this.uuid.equals(that.uuid); + } + + /** + * @see #uuid + */ + public int hashCode() + { + return uuid.hashCode(); + } + + /** + * Does this Session contain any changes which must be + * synchronized with the store? + * + * @return true => changes are pending + */ + public boolean isDirty() + { + // create a callback for the task + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + return session.isDirty(); + } + }; + // execute the callback + return ((Boolean)getHibernateTemplate().execute(callback)).booleanValue(); + } + + /** + * Just flushes the session + */ + public void flush() + { + getSession().flush(); + } + + public void setProtocolToACLDAO(Map map) + { + fProtocolToACLDAO = map; + } + + public void setDefaultACLDAO(AccessControlListDAO defaultACLDAO) + { + fDefaultACLDAO = defaultACLDAO; + } + + public NodePermissionEntry getPermissions(NodeRef nodeRef) + { + // Create the object if it is not found. + // Null objects are not cached in hibernate + // If the object does not exist it will repeatedly query to check its + // non existence. + NodePermissionEntry npe = null; + DbAccessControlList acl = null; + try + { + acl = getAccessControlList(nodeRef, false); + } + catch (InvalidNodeRefException e) + { + // Do nothing. + } + if (acl == null) + { + // there isn't an access control list for the node - spoof a null one + SimpleNodePermissionEntry snpe = new SimpleNodePermissionEntry( + nodeRef, + true, + Collections. emptySet()); + npe = snpe; + } + else + { + npe = createSimpleNodePermissionEntry(nodeRef); + } + // done + if (logger.isDebugEnabled()) + { + logger.debug( + "Created access control list for node: \n" + + " node: " + nodeRef + "\n" + + " acl: " + npe); + } + return npe; + } + + /** + * Get the persisted access control list or create it if required. + * + * @param nodeRef - the node for which to create the list + * @param create - create the object if it is missing + * @return Returns the current access control list or null if not found + */ + private DbAccessControlList getAccessControlList(NodeRef nodeRef, boolean create) + { + DbAccessControlList acl = + getACLDAO(nodeRef).getAccessControlList(nodeRef); + if (acl == null && create) + { + acl = createAccessControlList(nodeRef); + } + // done + if (logger.isDebugEnabled()) + { + logger.debug("Retrieved access control list: \n" + + " node: " + nodeRef + "\n" + + " list: " + acl); + } + return acl; + } + + /** + * Creates an access control list for the node and removes the entry from + * the nullPermsionCache. + */ + private DbAccessControlList createAccessControlList(NodeRef nodeRef) + { + DbAccessControlList acl = new DbAccessControlListImpl(); + acl.setInherits(INHERIT_PERMISSIONS_DEFAULT); + getHibernateTemplate().save(acl); + + // maintain inverse + getACLDAO(nodeRef).setAccessControlList(nodeRef, acl); + + // done + if (logger.isDebugEnabled()) + { + logger.debug("Created Access Control List: \n" + + " node: " + nodeRef + "\n" + + " list: " + acl); + } + return acl; + } + + public void deletePermissions(NodeRef nodeRef) + { + DbAccessControlList acl = null; + try + { + acl = getAccessControlList(nodeRef, false); + } + catch (InvalidNodeRefException e) + { + return; + } + if (acl != null) + { + // maintain referencial integrity + getACLDAO(nodeRef).setAccessControlList(nodeRef, null); + // delete the access control list - it will cascade to the entries + getHibernateTemplate().delete(acl); + } + } + + @SuppressWarnings("unchecked") + public void deletePermissions(final String authority) + { + // get the authority + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(QUERY_GET_AC_ENTRIES_FOR_AUTHORITY) + .setString("authorityRecipient", authority); + return (Integer) HibernateHelper.deleteDbAccessControlEntries(session, query); + } + }; + Integer deletedCount = (Integer) getHibernateTemplate().execute(callback); + // done + if (logger.isDebugEnabled()) + { + logger.debug("Deleted " + deletedCount + " entries for authority " + authority); + } + } + + public void deletePermissions(final NodeRef nodeRef, final String authority) + { + DbAccessControlList acl = null; + try + { + acl = getACLDAO(nodeRef).getAccessControlList(nodeRef); + } + catch (InvalidNodeRefException e) + { + return; + } + int deletedCount = 0; + if (acl != null) + { + deletedCount = acl.deleteEntriesForAuthority(authority); + } + // done + if (logger.isDebugEnabled()) + { + logger.debug("Deleted " + deletedCount + "entries for criteria: \n" + + " node: " + nodeRef + "\n" + + " authority: " + authority); + } + } + + /** + * Deletes all permission entries (access control list entries) that match + * the given criteria. Note that the access control list for the node is + * not deleted. + */ + public void deletePermission(NodeRef nodeRef, String authority, PermissionReference permission) + { + DbAccessControlList acl = null; + try + { + acl = getACLDAO(nodeRef).getAccessControlList(nodeRef); + } + catch (InvalidNodeRefException e) + { + return; + } + int deletedCount = 0; + if (acl != null) + { + DbPermissionKey permissionKey = new DbPermissionKey(permission.getQName(), permission.getName()); + deletedCount = acl.deleteEntry(authority, permissionKey); + } + // done + if (logger.isDebugEnabled()) + { + logger.debug("Deleted " + deletedCount + "entries for criteria: \n" + + " node: " + nodeRef + "\n" + + " permission: " + permission + "\n" + + " authority: " + authority); + } + } + + public void setPermission(NodeRef nodeRef, String authority, PermissionReference permission, boolean allow) + { + // get the entry + DbAccessControlEntry entry = getAccessControlEntry(nodeRef, authority, permission); + if (entry == null) + { + // need to create it + DbAccessControlList dbAccessControlList = getAccessControlList(nodeRef, true); + DbPermission dbPermission = getPermission(permission, true); + DbAuthority dbAuthority = getAuthority(authority, true); + // set persistent objects + entry = dbAccessControlList.newEntry(dbPermission, dbAuthority, allow); + // done + if (logger.isDebugEnabled()) + { + logger.debug("Created new access control entry: " + entry); + } + } + else + { + entry.setAllowed(allow); + // done + if (logger.isDebugEnabled()) + { + logger.debug("Updated access control entry: " + entry); + } + } + } + + /** + * @param nodeRef the node against which to join + * @param authority the authority against which to join + * @param perm the permission against which to join + * @return Returns all access control entries that match the criteria + */ + private DbAccessControlEntry getAccessControlEntry( + NodeRef nodeRef, + String authority, + PermissionReference permission) + { + DbAccessControlList acl = getAccessControlList(nodeRef, false); + DbAccessControlEntry entry = null; + if (acl != null) + { + DbPermissionKey permissionKey = new DbPermissionKey(permission.getQName(), permission.getName()); + entry = acl.getEntry(authority, permissionKey); + } + // done + if (logger.isDebugEnabled()) + { + logger.debug("" + (entry == null ? "Did not find" : "Found") + " entry for criteria: \n" + + " node: " + nodeRef + "\n" + + " authority: " + authority + "\n" + + " permission: " + permission); + } + return entry; + } + + /** + * Utility method to find or create a persisted authority + */ + private DbAuthority getAuthority(String authority, boolean create) + { + DbAuthority entity = (DbAuthority) getHibernateTemplate().get(DbAuthorityImpl.class, authority); + if ((entity == null) && create) + { + entity = new DbAuthorityImpl(); + entity.setRecipient(authority); + getHibernateTemplate().save(entity); + return entity; + } + else + { + return entity; + } + } + + /** + * Utility method to find and optionally create a persisted permission. + */ + private DbPermission getPermission(PermissionReference permissionRef, final boolean create) + { + final QName qname = permissionRef.getQName(); + final String name = permissionRef.getName(); + Session session = getSession(); + + DbPermission dbPermission = DbPermissionImpl.find(session, qname, name); + + // create if necessary + if ((dbPermission == null) && create) + { + dbPermission = new DbPermissionImpl(); + dbPermission.setTypeQname(qname); + dbPermission.setName(name); + getHibernateTemplate().save(dbPermission); + } + return dbPermission; + } + + public void setPermission(PermissionEntry permissionEntry) + { + setPermission( + permissionEntry.getNodeRef(), + permissionEntry.getAuthority(), + permissionEntry.getPermissionReference(), + permissionEntry.isAllowed()); + } + + public void setPermission(NodePermissionEntry nodePermissionEntry) + { + NodeRef nodeRef = nodePermissionEntry.getNodeRef(); + + // Get the access control list + // Note the logic here requires to know whether it was created or not + DbAccessControlList acl = getAccessControlList(nodeRef, false); + if (acl != null) + { + // maintain referencial integrity + getACLDAO(nodeRef).setAccessControlList(nodeRef, null); + // drop the list + getHibernateTemplate().delete(acl); + } + // create the access control list + acl = createAccessControlList(nodeRef); + + // set attributes + acl.setInherits(nodePermissionEntry.inheritPermissions()); + + // add all entries + for (PermissionEntry pe : nodePermissionEntry.getPermissionEntries()) + { + PermissionReference permission = pe.getPermissionReference(); + String authority = pe.getAuthority(); + boolean isAllowed = pe.isAllowed(); + + DbPermission permissionEntity = getPermission(permission, true); + DbAuthority authorityEntity = getAuthority(authority, true); + + @SuppressWarnings("unused") + DbAccessControlEntryImpl entry = acl.newEntry(permissionEntity, authorityEntity, isAllowed); + } + } + + public void setInheritParentPermissions(NodeRef nodeRef, boolean inheritParentPermissions) + { + DbAccessControlList acl = null; + if (!inheritParentPermissions) + { + // Inheritance == true is the default, so only force a create of the ACL if the value false + acl = getAccessControlList(nodeRef, true); + acl.setInherits(false); + } + else + { + acl = getAccessControlList(nodeRef, false); + if (acl != null) + { + acl.setInherits(true); + } + } + } + + public boolean getInheritParentPermissions(NodeRef nodeRef) + { + DbAccessControlList acl = null; + try + { + acl = getAccessControlList(nodeRef, false); + } + catch (InvalidNodeRefException e) + { + return INHERIT_PERMISSIONS_DEFAULT; + } + if (acl == null) + { + return true; + } + else + { + return acl.getInherits(); + } + } + + // Utility methods to create simple detached objects for the outside world + // We do not pass out the hibernate objects + + private SimpleNodePermissionEntry createSimpleNodePermissionEntry(NodeRef nodeRef) + { + DbAccessControlList acl = + getACLDAO(nodeRef).getAccessControlList(nodeRef); + if (acl == null) + { + // there isn't an access control list for the node - spoof a null one + SimpleNodePermissionEntry snpe = new SimpleNodePermissionEntry( + nodeRef, + true, + Collections. emptySet()); + return snpe; + } + else + { + Set entries = acl.getEntries(); + SimpleNodePermissionEntry snpe = new SimpleNodePermissionEntry( + nodeRef, + acl.getInherits(), + createSimplePermissionEntries(nodeRef, entries)); + return snpe; + } + } + + /** + * @param entries access control entries + * @return Returns a unique set of entries that can be given back to the outside world + */ + private Set createSimplePermissionEntries(NodeRef nodeRef, + Collection entries) + { + if (entries == null) + { + return null; + } + HashSet spes = new HashSet(entries.size(), 1.0f); + if (entries.size() != 0) + { + for (DbAccessControlEntry entry : entries) + { + spes.add(createSimplePermissionEntry(nodeRef, entry)); + } + } + return spes; + } + + private static SimplePermissionEntry createSimplePermissionEntry(NodeRef nodeRef, + DbAccessControlEntry ace) + { + if (ace == null) + { + return null; + } + return new SimplePermissionEntry( + nodeRef, + createSimplePermissionReference(ace.getPermission()), + ace.getAuthority().getRecipient(), + ace.isAllowed() ? AccessStatus.ALLOWED : AccessStatus.DENIED); + } + + private static SimplePermissionReference createSimplePermissionReference(DbPermission perm) + { + if (perm == null) + { + return null; + } + return new SimplePermissionReference( + perm.getTypeQname(), + perm.getName()); + } + + /** + * Helper to choose appropriate NodeService for the given NodeRef + * @param nodeRef The NodeRef to dispatch from. + * @return The appropriate NodeService. + */ + private AccessControlListDAO getACLDAO(NodeRef nodeRef) + { + AccessControlListDAO ret = fProtocolToACLDAO.get(nodeRef.getStoreRef().getProtocol()); + if (ret == null) + { + return fDefaultACLDAO; + } + return ret; + } +} diff --git a/source/java/org/alfresco/repo/domain/hibernate/Transaction.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/Transaction.hbm.xml index 61d3314746..a3c9bad0c9 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/Transaction.hbm.xml +++ b/source/java/org/alfresco/repo/domain/hibernate/Transaction.hbm.xml @@ -29,7 +29,7 @@ unique="false" not-null="false" cascade="none" /> - + + + select + max(txn.id) + from + org.alfresco.repo.domain.hibernate.NodeStatusImpl as status + join status.transaction as txn + + select count(txn.id) @@ -90,6 +98,21 @@ ]]> + + :lastTxnId and + server.ipAddress != :serverIpAddress + order by + txn.id + ]]> + + select count(status.key.guid) @@ -98,9 +121,7 @@ join status.transaction as txn where txn.id = :txnId and - status.node is not null and - status.key.protocol = :protocol and - status.key.identifier = :identifier + status.node is not null @@ -111,9 +132,7 @@ join status.transaction as txn where txn.id = :txnId and - status.node is null and - status.key.protocol = :protocol and - status.key.identifier = :identifier + status.node is null diff --git a/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java b/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java index 6ab41fa9ff..88ea3cf2ad 100644 --- a/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java +++ b/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java @@ -1,539 +1,627 @@ -/* - * Copyright (C) 2006 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.repo.domain.schema; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileWriter; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Writer; -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.List; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.i18n.I18NUtil; -import org.alfresco.repo.admin.patch.impl.SchemaUpgradeScriptPatch; -import org.alfresco.util.TempFileProvider; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.hibernate.Session; -import org.hibernate.SessionFactory; -import org.hibernate.Transaction; -import org.hibernate.cfg.Configuration; -import org.hibernate.dialect.Dialect; -import org.hibernate.tool.hbm2ddl.DatabaseMetadata; -import org.hibernate.tool.hbm2ddl.SchemaExport; -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.core.io.support.ResourcePatternResolver; -import org.springframework.orm.hibernate3.LocalSessionFactoryBean; - -/** - * Bootstraps the schema and schema update. The schema is considered missing if the applied patch table - * is not present, and the schema is considered empty if the applied patch table is empty. - * - * @author Derek Hulley - */ -public class SchemaBootstrap implements ApplicationListener -{ - /** The placeholder for the configured Dialect class name: ${db.script.dialect} */ - private static final String PLACEHOLDER_SCRIPT_DIALECT = "\\$\\{db\\.script\\.dialect\\}"; - - private static final String MSG_EXECUTING_SCRIPT = "schema.update.msg.executing_script"; - private static final String ERR_UPDATE_FAILED = "schema.update.err.update_failed"; - private static final String ERR_VALIDATION_FAILED = "schema.update.err.validation_failed"; - private static final String ERR_SCRIPT_NOT_RUN = "schema.update.err.update_script_not_run"; - private static final String ERR_SCRIPT_NOT_FOUND = "schema.update.err.script_not_found"; - private static final String ERR_STATEMENT_TERMINATOR = "schema.update.err.statement_terminator"; - - private static Log logger = LogFactory.getLog(SchemaBootstrap.class); - - private LocalSessionFactoryBean localSessionFactory; - private String schemaOuputFilename; - private boolean updateSchema; - private List postCreateScriptUrls; - private List validateUpdateScriptPatches; - private List applyUpdateScriptPatches; - - public SchemaBootstrap() - { - postCreateScriptUrls = new ArrayList(1); - validateUpdateScriptPatches = new ArrayList(4); - applyUpdateScriptPatches = new ArrayList(4); - } - - public void setLocalSessionFactory(LocalSessionFactoryBean localSessionFactory) throws BeansException - { - this.localSessionFactory = localSessionFactory; - } - - /** - * Set this to output the full database creation script - * - * @param schemaOuputFilename the name of a file to dump the schema to, or null to ignore - */ - public void setSchemaOuputFilename(String schemaOuputFilename) - { - this.schemaOuputFilename = schemaOuputFilename; - } - - /** - * Set whether to modify the schema or not. Either way, the schema will be validated. - * - * @param updateSchema true to update and validate the schema, otherwise false to just - * validate the schema. Default is true. - */ - public void setUpdateSchema(boolean updateSchema) - { - this.updateSchema = updateSchema; - } - - /** - * Set the scripts that must be executed after the schema has been created. - * - * @param postCreateScriptUrls file URLs - * - * @see #PLACEHOLDER_SCRIPT_DIALECT - */ - public void setPostCreateScriptUrls(List postUpdateScriptUrls) - { - this.postCreateScriptUrls = postUpdateScriptUrls; - } - - /** - * Set the schema script patches that must have been applied. These will not be - * applied to the database. These can be used where the script cannot be - * applied automatically or where a particular upgrade path is no longer supported. - * For example, at version 3.0, the upgrade scripts for version 1.4 may be considered - * unsupported - this doesn't prevent the manual application of the scripts, though. - * - * @param applyUpdateScriptPatches a list of schema patches to check - */ - public void setValidateUpdateScriptPatches(List scriptPatches) - { - this.validateUpdateScriptPatches = scriptPatches; - } - - /** - * Set the schema script patches that may be executed during an update. - * - * @param applyUpdateScriptPatches a list of schema patches to check - */ - public void setApplyUpdateScriptPatches(List scriptPatches) - { - this.applyUpdateScriptPatches = scriptPatches; - } - - public void onApplicationEvent(ApplicationEvent event) - { - if (!(event instanceof ContextRefreshedEvent)) - { - // only work on startup - return; - } - - // do everything in a transaction - Session session = getLocalSessionFactory().openSession(); - Transaction transaction = session.beginTransaction(); - try - { - // make sure that we don't autocommit - Connection connection = session.connection(); - connection.setAutoCommit(false); - - Configuration cfg = localSessionFactory.getConfiguration(); - // dump the schema, if required - if (schemaOuputFilename != null) - { - File schemaOutputFile = new File(schemaOuputFilename); - dumpSchemaCreate(cfg, schemaOutputFile); - } - - // update the schema, if required - if (updateSchema) - { - updateSchema(cfg, session, connection); - } - - // verify that all patches have been applied correctly - checkSchemaPatchScripts(cfg, session, connection, validateUpdateScriptPatches, false); // check scripts - checkSchemaPatchScripts(cfg, session, connection, applyUpdateScriptPatches, false); // check scripts - - // all done successfully - transaction.commit(); - } - catch (Throwable e) - { - try { transaction.rollback(); } catch (Throwable ee) {} - if (updateSchema) - { - throw new AlfrescoRuntimeException(ERR_UPDATE_FAILED, e); - } - else - { - throw new AlfrescoRuntimeException(ERR_VALIDATION_FAILED, e); - } - } - } - - private void dumpSchemaCreate(Configuration cfg, File schemaOutputFile) - { - // if the file exists, delete it - if (schemaOutputFile.exists()) - { - schemaOutputFile.delete(); - } - SchemaExport schemaExport = new SchemaExport(cfg) - .setFormat(true) - .setHaltOnError(true) - .setOutputFile(schemaOutputFile.getAbsolutePath()) - .setDelimiter(";"); - schemaExport.execute(false, false, false, true); - } - - private SessionFactory getLocalSessionFactory() - { - return (SessionFactory) localSessionFactory.getObject(); - } - - /** - * @return Returns the number of applied patches - */ - private int countAppliedPatches(Connection connection) throws Exception - { - Statement stmt = connection.createStatement(); - try - { - ResultSet rs = stmt.executeQuery("select count(id) from alf_applied_patch"); - rs.next(); - int count = rs.getInt(1); - return count; - } - catch (Throwable e) - { - // we'll try another table name - } - finally - { - try { stmt.close(); } catch (Throwable e) {} - } - // for pre-1.4 databases, the table was named differently - stmt = connection.createStatement(); - try - { - ResultSet rs = stmt.executeQuery("select count(id) from applied_patch"); - rs.next(); - int count = rs.getInt(1); - return count; - } - finally - { - try { stmt.close(); } catch (Throwable e) {} - } - } - - /** - * @return Returns the number of applied patches - */ - private boolean didPatchSucceed(Connection connection, String patchId) throws Exception - { - Statement stmt = connection.createStatement(); - try - { - ResultSet rs = stmt.executeQuery("select succeeded from alf_applied_patch where id = '" + patchId + "'"); - if (!rs.next()) - { - return false; - } - boolean succeeded = rs.getBoolean(1); - return succeeded; - } - catch (Throwable e) - { - // we'll try another table name - } - finally - { - try { stmt.close(); } catch (Throwable e) {} - } - // for pre-1.4 databases, the table was named differently - stmt = connection.createStatement(); - try - { - ResultSet rs = stmt.executeQuery("select succeeded from applied_patch where id = '" + patchId + "'"); - if (!rs.next()) - { - return false; - } - boolean succeeded = rs.getBoolean(1); - return succeeded; - } - finally - { - try { stmt.close(); } catch (Throwable e) {} - } - } - - /** - * Builds the schema from scratch or applies the necessary patches to the schema. - */ - private void updateSchema(Configuration cfg, Session session, Connection connection) throws Exception - { - boolean create = false; - try - { - countAppliedPatches(connection); - } - catch (Throwable e) - { - create = true; - } - if (create) - { - // Get the dialect - final Dialect dialect = Dialect.getDialect(cfg.getProperties()); - String dialectStr = dialect.getClass().getName(); - - // the applied patch table is missing - we assume that all other tables are missing - // perform a full update using Hibernate-generated statements - File tempFile = TempFileProvider.createTempFile("AlfrescoSchemaCreate-" + dialectStr + "-", ".sql"); - dumpSchemaCreate(cfg, tempFile); - FileInputStream tempInputStream = new FileInputStream(tempFile); - executeScriptFile(cfg, connection, tempInputStream, tempFile.getPath()); - // execute post-create scripts (not patches) - for (String scriptUrl : this.postCreateScriptUrls) - { - executeScriptUrl(cfg, connection, scriptUrl); - } - } - else - { - // we have a database, so just run the update scripts - checkSchemaPatchScripts(cfg, session, connection, validateUpdateScriptPatches, false); // check for scripts that must have been run - checkSchemaPatchScripts(cfg, session, connection, applyUpdateScriptPatches, true); // execute scripts as required - // let Hibernate do any required updates - File tempFile = null; - Writer writer = null; - try - { - final Dialect dialect = Dialect.getDialect(cfg.getProperties()); - DatabaseMetadata metadata = new DatabaseMetadata(connection, dialect); - String[] sqls = cfg.generateSchemaUpdateScript(dialect, metadata); - if (sqls.length > 0) - { - tempFile = TempFileProvider.createTempFile("AlfrescoSchemaUpdate", ".sql"); - writer = new BufferedWriter(new FileWriter(tempFile)); - for (String sql : sqls) - { - writer.append(sql); - writer.append(";\n"); - } - } - } - finally - { - if (writer != null) - { - try {writer.close();} catch (Throwable e) {} - } - } - // execute if there were changes raised by Hibernate - if (tempFile != null) - { - InputStream tempInputStream = new FileInputStream(tempFile); - executeScriptFile(cfg, connection, tempInputStream, tempFile.getPath()); - } - } - } - - /** - * Check that the necessary scripts have been executed against the database - */ - private void checkSchemaPatchScripts( - Configuration cfg, - Session session, - Connection connection, - List scriptPatches, - boolean apply) throws Exception - { - // first check if there have been any applied patches - int appliedPatchCount = countAppliedPatches(connection); - if (appliedPatchCount == 0) - { - // This is a new schema, so upgrade scripts are irrelevant - // and patches will not have been applied yet - return; - } - - for (SchemaUpgradeScriptPatch patch : scriptPatches) - { - final String patchId = patch.getId(); - final String scriptUrl = patch.getScriptUrl(); - - // check if the script was successfully executed - boolean wasSuccessfullyApplied = didPatchSucceed(connection, patchId); - if (wasSuccessfullyApplied) - { - // nothing to do - it has been done before - continue; - } - else if (!apply) - { - // the script was not run and may not be run automatically - throw AlfrescoRuntimeException.create(ERR_SCRIPT_NOT_RUN, scriptUrl); - } - // it wasn't run and it can be run now - executeScriptUrl(cfg, connection, scriptUrl); - } - } - - private void executeScriptUrl(Configuration cfg, Connection connection, String scriptUrl) throws Exception - { - Dialect dialect = Dialect.getDialect(cfg.getProperties()); - InputStream scriptInputStream = getScriptInputStream(dialect.getClass(), scriptUrl); - // check that it exists - if (scriptInputStream == null) - { - throw AlfrescoRuntimeException.create(ERR_SCRIPT_NOT_FOUND, scriptUrl); - } - // now execute it - executeScriptFile(cfg, connection, scriptInputStream, scriptUrl); - } - - /** - * Replaces the dialect placeholder in the script URL and attempts to find a file for - * it. If not found, the dialect hierarchy will be walked until a compatible script is - * found. This makes it possible to have scripts that are generic to all dialects. - * - * @return Returns an input stream onto the script, otherwise null - */ - private InputStream getScriptInputStream(Class dialectClazz, String scriptUrl) throws Exception - { - // replace the dialect placeholder - String dialectScriptUrl = scriptUrl.replaceAll(PLACEHOLDER_SCRIPT_DIALECT, dialectClazz.getName()); - // get a handle on the resource - ResourcePatternResolver rpr = new PathMatchingResourcePatternResolver(this.getClass().getClassLoader()); - Resource resource = rpr.getResource(dialectScriptUrl); - if (!resource.exists()) - { - // it wasn't found. Get the superclass of the dialect and try again - Class superClazz = dialectClazz.getSuperclass(); - if (Dialect.class.isAssignableFrom(superClazz)) - { - // we still have a Dialect - try again - return getScriptInputStream(superClazz, scriptUrl); - } - else - { - // we have exhausted all options - return null; - } - } - else - { - // we have a handle to it - return resource.getInputStream(); - } - } - - private void executeScriptFile( - Configuration cfg, - Connection connection, - InputStream scriptInputStream, - String scriptUrl) throws Exception - { - logger.info(I18NUtil.getMessage(MSG_EXECUTING_SCRIPT, scriptUrl)); - - BufferedReader reader = new BufferedReader(new InputStreamReader(scriptInputStream, "UTF8")); - try - { - int line = 0; - // loop through all statements - StringBuilder sb = new StringBuilder(1024); - while(true) - { - String sql = reader.readLine(); - line++; - - if (sql == null) - { - // nothing left in the file - break; - } - - // trim it - sql = sql.trim(); - if (sql.length() == 0 || - sql.startsWith( "--" ) || - sql.startsWith( "//" ) || - sql.startsWith( "/*" ) ) - { - if (sb.length() > 0) - { - // we have an unterminated statement - throw AlfrescoRuntimeException.create(ERR_STATEMENT_TERMINATOR, (line - 1), scriptUrl); - } - // there has not been anything to execute - it's just a comment line - continue; - } - // have we reached the end of a statement? - boolean execute = false; - if (sql.endsWith(";")) - { - sql = sql.substring(0, sql.length() - 1); - execute = true; - } - // append to the statement being built up - sb.append(" ").append(sql); - // execute, if required - if (execute) - { - Statement stmt = connection.createStatement(); - try - { - sql = sb.toString(); - if (logger.isDebugEnabled()) - { - logger.debug("Executing statment: " + sql); - } - stmt.execute(sql); - sb = new StringBuilder(1024); - } - finally - { - try { stmt.close(); } catch (Throwable e) {} - } - } - } - } - finally - { - try { reader.close(); } catch (Throwable e) {} - try { scriptInputStream.close(); } catch (Throwable e) {} - } - } -} +/* + * Copyright (C) 2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.domain.schema; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Writer; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.admin.patch.impl.SchemaUpgradeScriptPatch; +import org.alfresco.repo.content.filestore.FileContentWriter; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.util.AbstractLifecycleBean; +import org.alfresco.util.TempFileProvider; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.Transaction; +import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.Dialect; +import org.hibernate.tool.hbm2ddl.DatabaseMetadata; +import org.hibernate.tool.hbm2ddl.SchemaExport; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationEvent; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.orm.hibernate3.LocalSessionFactoryBean; + +/** + * Bootstraps the schema and schema update. The schema is considered missing if the applied patch table + * is not present, and the schema is considered empty if the applied patch table is empty. + * + * @author Derek Hulley + */ +public class SchemaBootstrap extends AbstractLifecycleBean +{ + /** The placeholder for the configured Dialect class name: ${db.script.dialect} */ + private static final String PLACEHOLDER_SCRIPT_DIALECT = "\\$\\{db\\.script\\.dialect\\}"; + + private static final String MSG_EXECUTING_SCRIPT = "schema.update.msg.executing_script"; + private static final String MSG_OPTIONAL_STATEMENT_FAILED = "schema.update.msg.optional_statement_failed"; + private static final String ERR_STATEMENT_FAILED = "schema.update.err.statement_failed"; + private static final String ERR_UPDATE_FAILED = "schema.update.err.update_failed"; + private static final String ERR_VALIDATION_FAILED = "schema.update.err.validation_failed"; + private static final String ERR_SCRIPT_NOT_RUN = "schema.update.err.update_script_not_run"; + private static final String ERR_SCRIPT_NOT_FOUND = "schema.update.err.script_not_found"; + private static final String ERR_STATEMENT_TERMINATOR = "schema.update.err.statement_terminator"; + + private static Log logger = LogFactory.getLog(SchemaBootstrap.class); + + private LocalSessionFactoryBean localSessionFactory; + private String schemaOuputFilename; + private boolean updateSchema; + private List postCreateScriptUrls; + private List validateUpdateScriptPatches; + private List applyUpdateScriptPatches; + + public SchemaBootstrap() + { + postCreateScriptUrls = new ArrayList(1); + validateUpdateScriptPatches = new ArrayList(4); + applyUpdateScriptPatches = new ArrayList(4); + } + + public void setLocalSessionFactory(LocalSessionFactoryBean localSessionFactory) throws BeansException + { + this.localSessionFactory = localSessionFactory; + } + + /** + * Set this to output the full database creation script + * + * @param schemaOuputFilename the name of a file to dump the schema to, or null to ignore + */ + public void setSchemaOuputFilename(String schemaOuputFilename) + { + this.schemaOuputFilename = schemaOuputFilename; + } + + /** + * Set whether to modify the schema or not. Either way, the schema will be validated. + * + * @param updateSchema true to update and validate the schema, otherwise false to just + * validate the schema. Default is true. + */ + public void setUpdateSchema(boolean updateSchema) + { + this.updateSchema = updateSchema; + } + + /** + * Set the scripts that must be executed after the schema has been created. + * + * @param postCreateScriptUrls file URLs + * + * @see #PLACEHOLDER_SCRIPT_DIALECT + */ + public void setPostCreateScriptUrls(List postUpdateScriptUrls) + { + this.postCreateScriptUrls = postUpdateScriptUrls; + } + + /** + * Set the schema script patches that must have been applied. These will not be + * applied to the database. These can be used where the script cannot be + * applied automatically or where a particular upgrade path is no longer supported. + * For example, at version 3.0, the upgrade scripts for version 1.4 may be considered + * unsupported - this doesn't prevent the manual application of the scripts, though. + * + * @param applyUpdateScriptPatches a list of schema patches to check + */ + public void setValidateUpdateScriptPatches(List scriptPatches) + { + this.validateUpdateScriptPatches = scriptPatches; + } + + /** + * Set the schema script patches that may be executed during an update. + * + * @param applyUpdateScriptPatches a list of schema patches to check + */ + public void setApplyUpdateScriptPatches(List scriptPatches) + { + this.applyUpdateScriptPatches = scriptPatches; + } + + private void dumpSchemaCreate(Configuration cfg, File schemaOutputFile) + { + // if the file exists, delete it + if (schemaOutputFile.exists()) + { + schemaOutputFile.delete(); + } + SchemaExport schemaExport = new SchemaExport(cfg) + .setFormat(true) + .setHaltOnError(true) + .setOutputFile(schemaOutputFile.getAbsolutePath()) + .setDelimiter(";"); + schemaExport.execute(false, false, false, true); + } + + private SessionFactory getLocalSessionFactory() + { + return (SessionFactory) localSessionFactory.getObject(); + } + + private static class NoSchemaException extends Exception + { + private static final long serialVersionUID = 5574280159910824660L; + } + + /** + * @return Returns the number of applied patches + */ + private int countAppliedPatches(Connection connection) throws Exception + { + DatabaseMetaData dbMetadata = connection.getMetaData(); + + ResultSet tableRs = dbMetadata.getTables(null, null, "%", null); + boolean newPatchTable = false; + boolean oldPatchTable = false; + try + { + while (tableRs.next()) + { + String tableName = tableRs.getString("TABLE_NAME"); + if (tableName.equalsIgnoreCase("applied_patch")) + { + oldPatchTable = true; + break; + } + else if (tableName.equalsIgnoreCase("alf_applied_patch")) + { + newPatchTable = true; + break; + } + } + } + finally + { + try { tableRs.close(); } catch (Throwable e) {e.printStackTrace(); } + } + + if (newPatchTable) + { + Statement stmt = connection.createStatement(); + try + { + ResultSet rs = stmt.executeQuery("select count(id) from alf_applied_patch"); + rs.next(); + int count = rs.getInt(1); + return count; + } + finally + { + try { stmt.close(); } catch (Throwable e) {} + } + } + else if (oldPatchTable) + { + // found the old style table name + Statement stmt = connection.createStatement(); + try + { + ResultSet rs = stmt.executeQuery("select count(id) from applied_patch"); + rs.next(); + int count = rs.getInt(1); + return count; + } + finally + { + try { stmt.close(); } catch (Throwable e) {} + } + } + else + { + // The applied patches table is not present + throw new NoSchemaException(); + } + } + + /** + * @return Returns the number of applied patches + */ + private boolean didPatchSucceed(Connection connection, String patchId) throws Exception + { + Statement stmt = connection.createStatement(); + try + { + ResultSet rs = stmt.executeQuery("select succeeded from alf_applied_patch where id = '" + patchId + "'"); + if (!rs.next()) + { + return false; + } + boolean succeeded = rs.getBoolean(1); + return succeeded; + } + catch (Throwable e) + { + // we'll try another table name + } + finally + { + try { stmt.close(); } catch (Throwable e) {} + } + // for pre-1.4 databases, the table was named differently + stmt = connection.createStatement(); + try + { + ResultSet rs = stmt.executeQuery("select succeeded from applied_patch where id = '" + patchId + "'"); + if (!rs.next()) + { + return false; + } + boolean succeeded = rs.getBoolean(1); + return succeeded; + } + finally + { + try { stmt.close(); } catch (Throwable e) {} + } + } + + /** + * Builds the schema from scratch or applies the necessary patches to the schema. + */ + private void updateSchema(Configuration cfg, Session session, Connection connection) throws Exception + { + boolean create = false; + try + { + countAppliedPatches(connection); + } + catch (NoSchemaException e) + { + create = true; + } + // Get the dialect + final Dialect dialect = Dialect.getDialect(cfg.getProperties()); + String dialectStr = dialect.getClass().getName(); + + if (create) + { + // the applied patch table is missing - we assume that all other tables are missing + // perform a full update using Hibernate-generated statements + File tempFile = TempFileProvider.createTempFile("AlfrescoSchemaCreate-" + dialectStr + "-", ".sql"); + dumpSchemaCreate(cfg, tempFile); + executeScriptFile(cfg, connection, tempFile, tempFile.getPath()); + // execute post-create scripts (not patches) + for (String scriptUrl : this.postCreateScriptUrls) + { + executeScriptUrl(cfg, connection, scriptUrl); + } + } + else + { + // we have a database, so just run the update scripts + checkSchemaPatchScripts(cfg, session, connection, validateUpdateScriptPatches, false); // check for scripts that must have been run + checkSchemaPatchScripts(cfg, session, connection, applyUpdateScriptPatches, true); // execute scripts as required + // let Hibernate do any required updates + File tempFile = null; + Writer writer = null; + try + { + DatabaseMetadata metadata = new DatabaseMetadata(connection, dialect); + String[] sqls = cfg.generateSchemaUpdateScript(dialect, metadata); + if (sqls.length > 0) + { + tempFile = TempFileProvider.createTempFile("AlfrescoSchemaUpdate-" + dialectStr + "-", ".sql"); + writer = new BufferedWriter(new FileWriter(tempFile)); + for (String sql : sqls) + { + writer.append(sql); + writer.append(";\n"); + } + } + } + finally + { + if (writer != null) + { + try {writer.close();} catch (Throwable e) {} + } + } + // execute if there were changes raised by Hibernate + if (tempFile != null) + { + executeScriptFile(cfg, connection, tempFile, tempFile.getPath()); + } + } + } + + /** + * Check that the necessary scripts have been executed against the database + */ + private void checkSchemaPatchScripts( + Configuration cfg, + Session session, + Connection connection, + List scriptPatches, + boolean apply) throws Exception + { + // first check if there have been any applied patches + int appliedPatchCount = countAppliedPatches(connection); + if (appliedPatchCount == 0) + { + // This is a new schema, so upgrade scripts are irrelevant + // and patches will not have been applied yet + return; + } + + for (SchemaUpgradeScriptPatch patch : scriptPatches) + { + final String patchId = patch.getId(); + final String scriptUrl = patch.getScriptUrl(); + + // check if the script was successfully executed + boolean wasSuccessfullyApplied = didPatchSucceed(connection, patchId); + if (wasSuccessfullyApplied) + { + // nothing to do - it has been done before + continue; + } + else if (!apply) + { + // the script was not run and may not be run automatically + throw AlfrescoRuntimeException.create(ERR_SCRIPT_NOT_RUN, scriptUrl); + } + // it wasn't run and it can be run now + executeScriptUrl(cfg, connection, scriptUrl); + } + } + + private void executeScriptUrl(Configuration cfg, Connection connection, String scriptUrl) throws Exception + { + Dialect dialect = Dialect.getDialect(cfg.getProperties()); + String dialectStr = dialect.getClass().getName(); + InputStream scriptInputStream = getScriptInputStream(dialect.getClass(), scriptUrl); + // check that it exists + if (scriptInputStream == null) + { + throw AlfrescoRuntimeException.create(ERR_SCRIPT_NOT_FOUND, scriptUrl); + } + // write the script to a temp location for future and failure reference + File tempFile = null; + try + { + tempFile = TempFileProvider.createTempFile("AlfrescoSchemaUpdate-" + dialectStr + "-", ".sql"); + ContentWriter writer = new FileContentWriter(tempFile); + writer.putContent(scriptInputStream); + } + finally + { + try { scriptInputStream.close(); } catch (Throwable e) {} // usually a duplicate close + } + // now execute it + executeScriptFile(cfg, connection, tempFile, scriptUrl); + } + + /** + * Replaces the dialect placeholder in the script URL and attempts to find a file for + * it. If not found, the dialect hierarchy will be walked until a compatible script is + * found. This makes it possible to have scripts that are generic to all dialects. + * + * @return Returns an input stream onto the script, otherwise null + */ + private InputStream getScriptInputStream(Class dialectClazz, String scriptUrl) throws Exception + { + // replace the dialect placeholder + String dialectScriptUrl = scriptUrl.replaceAll(PLACEHOLDER_SCRIPT_DIALECT, dialectClazz.getName()); + // get a handle on the resource + ResourcePatternResolver rpr = new PathMatchingResourcePatternResolver(this.getClass().getClassLoader()); + Resource resource = rpr.getResource(dialectScriptUrl); + if (!resource.exists()) + { + // it wasn't found. Get the superclass of the dialect and try again + Class superClazz = dialectClazz.getSuperclass(); + if (Dialect.class.isAssignableFrom(superClazz)) + { + // we still have a Dialect - try again + return getScriptInputStream(superClazz, scriptUrl); + } + else + { + // we have exhausted all options + return null; + } + } + else + { + // we have a handle to it + return resource.getInputStream(); + } + } + + private void executeScriptFile( + Configuration cfg, + Connection connection, + File scriptFile, + String scriptUrl) throws Exception + { + logger.info(I18NUtil.getMessage(MSG_EXECUTING_SCRIPT, scriptUrl)); + + InputStream scriptInputStream = new FileInputStream(scriptFile); + BufferedReader reader = new BufferedReader(new InputStreamReader(scriptInputStream, "UTF8")); + try + { + int line = 0; + // loop through all statements + StringBuilder sb = new StringBuilder(1024); + while(true) + { + String sql = reader.readLine(); + line++; + + if (sql == null) + { + // nothing left in the file + break; + } + + // trim it + sql = sql.trim(); + if (sql.length() == 0 || + sql.startsWith( "--" ) || + sql.startsWith( "//" ) || + sql.startsWith( "/*" ) ) + { + if (sb.length() > 0) + { + // we have an unterminated statement + throw AlfrescoRuntimeException.create(ERR_STATEMENT_TERMINATOR, (line - 1), scriptUrl); + } + // there has not been anything to execute - it's just a comment line + continue; + } + // have we reached the end of a statement? + boolean execute = false; + boolean optional = false; + if (sql.endsWith(";")) + { + sql = sql.substring(0, sql.length() - 1); + execute = true; + optional = false; + } + else if (sql.endsWith(";(optional)")) + { + sql = sql.substring(0, sql.length() - 11); + execute = true; + optional = true; + } + // append to the statement being built up + sb.append(" ").append(sql); + // execute, if required + if (execute) + { + sql = sb.toString(); + executeStatement(connection, sql, optional, line, scriptFile); + sb = new StringBuilder(1024); + } + } + } + finally + { + try { reader.close(); } catch (Throwable e) {} + try { scriptInputStream.close(); } catch (Throwable e) {} + } + } + + /** + * Execute the given SQL statement, absorbing exceptions that we expect during + * schema creation or upgrade. + */ + private void executeStatement(Connection connection, String sql, boolean optional, int line, File file) throws Exception + { + Statement stmt = connection.createStatement(); + try + { + if (logger.isDebugEnabled()) + { + logger.debug("Executing statment: " + sql); + } + stmt.execute(sql); + } + catch (SQLException e) + { + if (optional) + { + // it was marked as optional, so we just ignore it + String msg = I18NUtil.getMessage(MSG_OPTIONAL_STATEMENT_FAILED, sql, e.getMessage(), file.getAbsolutePath(), line); + logger.warn(msg); + } + else + { + String err = I18NUtil.getMessage(ERR_STATEMENT_FAILED, sql, e.getMessage(), file.getAbsolutePath(), line); + logger.error(err); + throw e; + } + } + finally + { + try { stmt.close(); } catch (Throwable e) {} + } + } + + @Override + protected void onBootstrap(ApplicationEvent event) + { + // do everything in a transaction + Session session = getLocalSessionFactory().openSession(); + Transaction transaction = session.beginTransaction(); + try + { + // make sure that we don't autocommit + Connection connection = session.connection(); + connection.setAutoCommit(false); + + Configuration cfg = localSessionFactory.getConfiguration(); + // dump the schema, if required + if (schemaOuputFilename != null) + { + File schemaOutputFile = new File(schemaOuputFilename); + dumpSchemaCreate(cfg, schemaOutputFile); + } + + // update the schema, if required + if (updateSchema) + { + updateSchema(cfg, session, connection); + } + + // verify that all patches have been applied correctly + checkSchemaPatchScripts(cfg, session, connection, validateUpdateScriptPatches, false); // check scripts + checkSchemaPatchScripts(cfg, session, connection, applyUpdateScriptPatches, false); // check scripts + + // all done successfully + transaction.commit(); + } + catch (Throwable e) + { + try { transaction.rollback(); } catch (Throwable ee) {} + if (updateSchema) + { + throw new AlfrescoRuntimeException(ERR_UPDATE_FAILED, e); + } + else + { + throw new AlfrescoRuntimeException(ERR_VALIDATION_FAILED, e); + } + } + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + // NOOP + } +} diff --git a/source/java/org/alfresco/repo/importer/ImporterBootstrap.java b/source/java/org/alfresco/repo/importer/ImporterBootstrap.java index 12556807e0..60f8e1fcba 100644 --- a/source/java/org/alfresco/repo/importer/ImporterBootstrap.java +++ b/source/java/org/alfresco/repo/importer/ImporterBootstrap.java @@ -25,6 +25,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; +import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Properties; @@ -46,6 +47,7 @@ import org.alfresco.service.cmr.view.Location; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.AbstractLifecycleBean; import org.alfresco.util.TempFileProvider; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -53,8 +55,6 @@ import org.apache.commons.logging.impl.Log4JLogger; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.util.FileCopyUtils; /** @@ -62,7 +62,7 @@ import org.springframework.util.FileCopyUtils; * * @author David Caruana */ -public class ImporterBootstrap implements ApplicationListener +public class ImporterBootstrap extends AbstractLifecycleBean { // View Properties (used in setBootstrapViews) public static final String VIEW_PATH_PROPERTY = "path"; @@ -83,6 +83,7 @@ public class ImporterBootstrap implements ApplicationListener private NodeService nodeService; private ImporterService importerService; private List bootstrapViews; + private List extensionBootstrapViews; private StoreRef storeRef = null; private List mustNotExistStoreUrls = null; private Properties configuration = null; @@ -176,6 +177,20 @@ public class ImporterBootstrap implements ApplicationListener this.bootstrapViews = bootstrapViews; } + /** + * Sets the bootstrap views + * + * @param bootstrapViews + */ + public void addBootstrapViews(List bootstrapViews) + { + if (this.extensionBootstrapViews == null) + { + this.extensionBootstrapViews = new ArrayList(); + } + this.extensionBootstrapViews.addAll(bootstrapViews); + } + /** * Sets the Store Ref to bootstrap into * @@ -348,6 +363,12 @@ public class ImporterBootstrap implements ApplicationListener // bootstrap the store contents if (bootstrapViews != null) { + // add-in any extended views + if (extensionBootstrapViews != null) + { + bootstrapViews.addAll(extensionBootstrapViews); + } + for (Properties bootstrapView : bootstrapViews) { String view = bootstrapView.getProperty(VIEW_LOCATION_VIEW); @@ -643,16 +664,16 @@ public class ImporterBootstrap implements ApplicationListener return true; } - /* - * (non-Javadoc) - * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) - */ - public void onApplicationEvent(ApplicationEvent event) + @Override + protected void onBootstrap(ApplicationEvent event) { - if (event instanceof ContextRefreshedEvent) - { - bootstrap(); - } + bootstrap(); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + // NOOP } } diff --git a/source/java/org/alfresco/repo/importer/ImporterBootstrapViews.java b/source/java/org/alfresco/repo/importer/ImporterBootstrapViews.java new file mode 100644 index 0000000000..a15f11da30 --- /dev/null +++ b/source/java/org/alfresco/repo/importer/ImporterBootstrapViews.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.importer; + +import java.util.List; +import java.util.Properties; + +import org.springframework.beans.factory.InitializingBean; + +/** + * Collection of views to import + * + * @author David Caruana + */ +public class ImporterBootstrapViews implements InitializingBean +{ + // Dependencies + private ImporterBootstrap importer; + private List bootstrapViews; + + + /** + * Sets the importer + * + * @param importer + */ + public void setImporter(ImporterBootstrap importer) + { + this.importer = importer; + } + + /** + * Sets the bootstrap views + * + * @param bootstrapViews + */ + public void setBootstrapViews(List bootstrapViews) + { + this.bootstrapViews = bootstrapViews; + } + + + public void afterPropertiesSet() throws Exception + { + importer.addBootstrapViews(bootstrapViews); + } + +} diff --git a/source/java/org/alfresco/repo/importer/system/SystemExporterImporter.java b/source/java/org/alfresco/repo/importer/system/SystemExporterImporter.java index 20a3271d59..adbc5320ba 100644 --- a/source/java/org/alfresco/repo/importer/system/SystemExporterImporter.java +++ b/source/java/org/alfresco/repo/importer/system/SystemExporterImporter.java @@ -22,7 +22,7 @@ import java.util.List; import org.alfresco.repo.admin.patch.PatchDaoService; import org.alfresco.repo.domain.AppliedPatch; -import org.alfresco.repo.domain.hibernate.VersionCounterDaoComponentImpl; +import org.alfresco.repo.version.common.counter.VersionCounterService; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; @@ -37,7 +37,7 @@ public class SystemExporterImporter // dependencies private NodeService nodeService; private PatchDaoService patchDao; - private VersionCounterDaoComponentImpl versionCounterDao; + private VersionCounterService versionCounterService; public void setNodeService(NodeService nodeService) @@ -50,9 +50,9 @@ public class SystemExporterImporter this.patchDao = patchDaoService; } - public void setVersionDao(VersionCounterDaoComponentImpl versionCounterDao) + public void setVersionCounterService(VersionCounterService versionCounterService) { - this.versionCounterDao = versionCounterDao; + this.versionCounterService = versionCounterService; } @@ -89,7 +89,7 @@ public class SystemExporterImporter for (StoreRef storeRef : storeRefs) { VersionCounterInfo versionCounterInfo = new VersionCounterInfo(); - int versionCount = versionCounterDao.currentVersionNumber(storeRef); + int versionCount = versionCounterService.currentVersionNumber(storeRef); versionCounterInfo.storeRef = storeRef.toString(); versionCounterInfo.count = versionCount; systemInfo.versionCounters.add(versionCounterInfo); @@ -128,7 +128,7 @@ public class SystemExporterImporter for (VersionCounterInfo versionCounterInfo : systemInfo.versionCounters) { StoreRef storeRef = new StoreRef(versionCounterInfo.storeRef); - versionCounterDao.setVersionNumber(storeRef, versionCounterInfo.count); + versionCounterService.setVersionNumber(storeRef, versionCounterInfo.count); } } diff --git a/source/java/org/alfresco/repo/importer/system/SystemInfoBootstrap.java b/source/java/org/alfresco/repo/importer/system/SystemInfoBootstrap.java index e764c2e751..5983a32379 100644 --- a/source/java/org/alfresco/repo/importer/system/SystemInfoBootstrap.java +++ b/source/java/org/alfresco/repo/importer/system/SystemInfoBootstrap.java @@ -26,9 +26,8 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.view.ImporterException; import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.AbstractLifecycleBean; import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.ContextRefreshedEvent; /** @@ -36,7 +35,7 @@ import org.springframework.context.event.ContextRefreshedEvent; * * @author davidc */ -public class SystemInfoBootstrap implements ApplicationListener +public class SystemInfoBootstrap extends AbstractLifecycleBean { // dependencies private TransactionService transactionService; @@ -177,16 +176,16 @@ public class SystemInfoBootstrap implements ApplicationListener return true; } - /* - * (non-Javadoc) - * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) - */ - public void onApplicationEvent(ApplicationEvent event) + @Override + protected void onBootstrap(ApplicationEvent event) { - if (event instanceof ContextRefreshedEvent) - { - bootstrap(); - } + bootstrap(); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + // NOOP } } diff --git a/source/java/org/alfresco/repo/jscript/Actions.java b/source/java/org/alfresco/repo/jscript/Actions.java index b371b1b5aa..a0f4734e97 100644 --- a/source/java/org/alfresco/repo/jscript/Actions.java +++ b/source/java/org/alfresco/repo/jscript/Actions.java @@ -28,32 +28,31 @@ import org.alfresco.service.cmr.action.ParameterDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.namespace.QName; import org.mozilla.javascript.Scriptable; - +import org.mozilla.javascript.Wrapper; /** * Scripted Action service for describing and executing actions against Nodes. - * + * * @author davidc */ -public final class Actions implements Scopeable +public final class Actions extends BaseScriptImplementation implements Scopeable { /** Repository Service Registry */ private ServiceRegistry services; - + /** Root scope for this object */ private Scriptable scope; - /** - * Constructor + * Set the service registry * - * @param services repository service registry + * @param serviceRegistry the service registry */ - public Actions(ServiceRegistry services) + public void setServiceRegistry(ServiceRegistry serviceRegistry) { - this.services = services; + this.services = serviceRegistry; } - + /** * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) */ @@ -65,7 +64,7 @@ public final class Actions implements Scopeable /** * Gets the list of registered action names * - * @return the registered action names + * @return the registered action names */ public String[] getRegistered() { @@ -79,17 +78,18 @@ public final class Actions implements Scopeable } return registered; } - + public String[] jsGet_registered() { return getRegistered(); } - + /** * Create an Action * - * @param actionName the action name - * @return the action + * @param actionName + * the action name + * @return the action */ public ScriptAction create(String actionName) { @@ -104,8 +104,7 @@ public final class Actions implements Scopeable } return scriptAction; } - - + /** * Scriptable Action * @@ -114,23 +113,25 @@ public final class Actions implements Scopeable public final class ScriptAction implements Serializable, Scopeable { private static final long serialVersionUID = 5794161358406531996L; - + /** Root scope for this object */ - private Scriptable scope; + private Scriptable scope; /** Converter with knowledge of action parameter values */ private ActionValueConverter converter; - + /** Action state */ private Action action; + private ActionDefinition actionDef; + private ScriptableParameterMap parameters = null; - - + /** * Construct * - * @param action Alfresco action + * @param action + * Alfresco action */ public ScriptAction(Action action, ActionDefinition actionDef) { @@ -138,7 +139,7 @@ public final class Actions implements Scopeable this.actionDef = actionDef; this.converter = new ActionValueConverter(); } - + /** * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) */ @@ -146,28 +147,25 @@ public final class Actions implements Scopeable { this.scope = scope; } - + /** * Returns the action name * - * @return action name + * @return action name */ public String getName() { return this.actionDef.getName(); } - + public String jsGet_name() { return getName(); } - + /** - * Return all the properties known about this node. - * - * The Map returned implements the Scriptable interface to allow access to the properties via - * JavaScript associative array access. This means properties of a node can be access thus: - * node.properties["name"] + * Return all the properties known about this node. The Map returned implements the Scriptable interface to allow access to the properties via JavaScript associative array + * access. This means properties of a node can be access thus: node.properties["name"] * * @return Map of properties for this Node. */ @@ -187,17 +185,18 @@ public final class Actions implements Scopeable this.parameters.setModified(false); } return this.parameters; - } - + } + public Map jsGet_parameters() { return getParameters(); } - + /** * Execute action * - * @param node the node to execute action upon + * @param node + * the node to execute action upon */ @SuppressWarnings("synthetic-access") public void execute(Node node) @@ -206,7 +205,7 @@ public final class Actions implements Scopeable { Map actionParams = action.getParameterValues(); actionParams.clear(); - + for (Map.Entry entry : this.parameters.entrySet()) { // perform the conversion from script wrapper object to repo serializable values @@ -216,8 +215,11 @@ public final class Actions implements Scopeable } } services.getActionService().executeAction(action, node.getNodeRef()); + + // Reset the actioned upon node + node.reset(); } - + /** * Value converter with specific knowledge of action parameters * @@ -227,10 +229,12 @@ public final class Actions implements Scopeable { /** * Convert Action Parameter for Script usage - * - * @param paramName parameter name - * @param value value to convert - * @return converted value + * + * @param paramName + * parameter name + * @param value + * value to convert + * @return converted value */ @SuppressWarnings("synthetic-access") public Serializable convertActionParamForScript(String paramName, Serializable value) @@ -238,7 +242,7 @@ public final class Actions implements Scopeable ParameterDefinition paramDef = actionDef.getParameterDefintion(paramName); if (paramDef != null && paramDef.getType().equals(DataTypeDefinition.QNAME)) { - return ((QName)value).toPrefixString(services.getNamespaceService()); + return ((QName) value).toPrefixString(services.getNamespaceService()); } else { @@ -249,17 +253,45 @@ public final class Actions implements Scopeable /** * Convert Action Parameter for Java usage * - * @param paramName parameter name - * @param value value to convert - * @return converted value + * @param paramName + * parameter name + * @param value + * value to convert + * @return converted value */ @SuppressWarnings("synthetic-access") public Serializable convertActionParamForRepo(String paramName, Serializable value) { ParameterDefinition paramDef = actionDef.getParameterDefintion(paramName); + if (paramDef != null && paramDef.getType().equals(DataTypeDefinition.QNAME)) { - return QName.createQName((String)value, services.getNamespaceService()); + if (value instanceof Wrapper) + { + // unwrap a Java object from a JavaScript wrapper + // recursively call this method to convert the unwrapped value + return convertActionParamForRepo(paramName, (Serializable) ((Wrapper) value).unwrap()); + } + else + { + if (value instanceof String) + { + String stringQName = (String) value; + if (stringQName.startsWith("{")) + { + return QName.createQName(stringQName); + + } + else + { + return QName.createQName(stringQName, services.getNamespaceService()); + } + } + else + { + return value; + } + } } else { @@ -269,39 +301,41 @@ public final class Actions implements Scopeable } } - /** * Scripted Parameter map with modified flag. - * + * * @author davidc */ - public static final class ScriptableParameterMap extends ScriptableHashMap + public static final class ScriptableParameterMap extends ScriptableHashMap { private static final long serialVersionUID = 574661815973241554L; - private boolean modified = false; + private boolean modified = false; /** * Is this a modified parameter map? * - * @return true => modified + * @return true => modified */ - /*package*/ boolean isModified() + /* package */boolean isModified() { return modified; } - + /** * Set explicitly whether this map is modified * - * @param modified true => modified, false => not modified + * @param modified + * true => modified, false => not modified */ - /*package*/ void setModified(boolean modified) + /* package */void setModified(boolean modified) { this.modified = modified; } - - /* (non-Javadoc) + + /* + * (non-Javadoc) + * * @see org.mozilla.javascript.Scriptable#getClassName() */ @Override @@ -310,7 +344,9 @@ public final class Actions implements Scopeable return "ScriptableParameterMap"; } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see org.mozilla.javascript.Scriptable#delete(java.lang.String) */ @Override @@ -320,7 +356,9 @@ public final class Actions implements Scopeable setModified(true); } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see org.mozilla.javascript.Scriptable#put(java.lang.String, org.mozilla.javascript.Scriptable, java.lang.Object) */ @Override @@ -330,5 +368,5 @@ public final class Actions implements Scopeable setModified(true); } } - + } diff --git a/source/java/org/alfresco/repo/jscript/BaseScriptImplementation.java b/source/java/org/alfresco/repo/jscript/BaseScriptImplementation.java new file mode 100644 index 0000000000..3183d2f16d --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/BaseScriptImplementation.java @@ -0,0 +1,57 @@ +/** + * + */ +package org.alfresco.repo.jscript; + +import org.alfresco.service.cmr.repository.ScriptImplementation; +import org.alfresco.service.cmr.repository.ScriptService; + +/** + * Abstract base class for a script implementation + * + * @author Roy Wetherall + */ +public abstract class BaseScriptImplementation implements ScriptImplementation +{ + /** The script service */ + private ScriptService scriptService; + + /** The name of the script */ + private String scriptName; + + /** + * Sets the script service + * + * @param scriptService the script service + */ + public void setScriptService(ScriptService scriptService) + { + this.scriptService = scriptService; + } + + /** + * Registers this script with the script service + */ + public void register() + { + this.scriptService.registerScript(this); + } + + /** + * Sets the script name + * + * @param scriptName the script name + */ + public void setScriptName(String scriptName) + { + this.scriptName = scriptName; + } + + /** + * @see org.alfresco.service.cmr.repository.ScriptImplementation#getScriptName() + */ + public String getScriptName() + { + return this.scriptName; + } +} diff --git a/source/java/org/alfresco/repo/jscript/CategoryNode.java b/source/java/org/alfresco/repo/jscript/CategoryNode.java new file mode 100644 index 0000000000..a22183ba61 --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/CategoryNode.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.jscript; + +import java.util.Collection; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.alfresco.service.cmr.search.CategoryService; +import org.alfresco.service.namespace.QName; +import org.mozilla.javascript.Scriptable; + +/** + * Category Nodes from the classification helper have special support. + * + * @author Andy Hind + */ +public class CategoryNode extends Node +{ + /** + * Constructor + * + * @param nodeRef + * @param services + * @param resolver + */ + public CategoryNode(NodeRef nodeRef, ServiceRegistry services, TemplateImageResolver resolver) + { + super(nodeRef, services, resolver); + } + + /** + * Constructor + * + * @param nodeRef + * @param services + * @param resolver + * @param scope + */ + public CategoryNode(NodeRef nodeRef, ServiceRegistry services, TemplateImageResolver resolver, Scriptable scope) + { + super(nodeRef, services, resolver, scope); + } + + /** + * @return all the members of a category + */ + public Node[] getCategoryMembers() + { + return buildNodes(services.getCategoryService().getChildren(getNodeRef(), CategoryService.Mode.MEMBERS, CategoryService.Depth.ANY)); + } + + public Node[] jsGet_categoryMembers() + { + return getCategoryMembers(); + } + + /** + * @return all the subcategories of a category + */ + public CategoryNode[] getSubCategories() + { + return buildCategoryNodes(services.getCategoryService().getChildren(getNodeRef(), CategoryService.Mode.SUB_CATEGORIES, CategoryService.Depth.ANY)); + } + + public CategoryNode[] jsGet_subCategories() + { + return getSubCategories(); + } + + /** + * @return members and subcategories of a category + */ + public Node[] getMembersAndSubCategories() + { + return buildMixedNodes(services.getCategoryService().getChildren(getNodeRef(), CategoryService.Mode.ALL, CategoryService.Depth.ANY)); + } + + public Node[] jsGet_membersAndSubCategories() + { + return getMembersAndSubCategories(); + } + + /** + * @return all the immediate member of a category + */ + public Node[] getImmediateCategoryMembers() + { + return buildNodes(services.getCategoryService().getChildren(getNodeRef(), CategoryService.Mode.MEMBERS, CategoryService.Depth.IMMEDIATE)); + } + + public Node[] jsGet_immediateCategoryMembers() + { + return getImmediateCategoryMembers(); + } + + /** + * @return all the immediate subcategories of a category + */ + public CategoryNode[] getImmediateSubCategories() + { + return buildCategoryNodes(services.getCategoryService().getChildren(getNodeRef(), CategoryService.Mode.SUB_CATEGORIES, CategoryService.Depth.IMMEDIATE)); + } + + public CategoryNode[] jsGet_immediateSubCategories() + { + return getImmediateSubCategories(); + } + + /** + * @return immediate members and subcategories of a category + */ + public Node[] getImmediateMembersAndSubCategories() + { + return buildMixedNodes(services.getCategoryService().getChildren(getNodeRef(), CategoryService.Mode.ALL, CategoryService.Depth.IMMEDIATE)); + } + + public Node[] jsGet_immediateMembersAndSubCategories() + { + return getImmediateMembersAndSubCategories(); + } + + /** + * Create a new subcategory + * + * @param name Of the category to create + * + * @return CategoryNode + */ + public CategoryNode createSubCategory(String name) + { + return new CategoryNode(services.getCategoryService().createCategory(getNodeRef(), name), this.services, this.imageResolver, this.scope); + } + + /** + * Remove this category + */ + public void removeCategory() + { + services.getCategoryService().deleteCategory(getNodeRef()); + } + + @Override + public boolean getIsCategory() + { + return true; + } + + private CategoryNode[] buildCategoryNodes(Collection cars) + { + CategoryNode[] categoryNodes = new CategoryNode[cars.size()]; + int i = 0; + for (ChildAssociationRef car : cars) + { + categoryNodes[i++] = new CategoryNode(car.getChildRef(), this.services, this.imageResolver, this.scope); + } + return categoryNodes; + } + + private Node[] buildNodes(Collection cars) + { + Node[] nodes = new Node[cars.size()]; + int i = 0; + for (ChildAssociationRef car : cars) + { + nodes[i++] = new Node(car.getChildRef(), this.services, this.imageResolver, this.scope); + } + return nodes; + } + + private Node[] buildMixedNodes(Collection cars) + { + Node[] nodes = new Node[cars.size()]; + int i = 0; + for (ChildAssociationRef car : cars) + { + QName type = services.getNodeService().getType(car.getChildRef()); + if (services.getDictionaryService().isSubClass(type, ContentModel.TYPE_CATEGORY)) + { + nodes[i++] = new CategoryNode(car.getChildRef(), this.services, this.imageResolver, this.scope); + } + else + { + nodes[i++] = new Node(car.getChildRef(), this.services, this.imageResolver, this.scope); + } + } + return nodes; + } +} diff --git a/source/java/org/alfresco/repo/jscript/CategoryTemplateNode.java b/source/java/org/alfresco/repo/jscript/CategoryTemplateNode.java new file mode 100644 index 0000000000..2db629bcc2 --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/CategoryTemplateNode.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.jscript; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.alfresco.service.cmr.repository.TemplateNode; +import org.alfresco.service.cmr.search.CategoryService; +import org.alfresco.service.namespace.QName; + +/** + * Category Nodes from the classification helper have special support. + */ +public class CategoryTemplateNode extends TemplateNode +{ + /** + * Constructor + * + * @param nodeRef + * @param services + * @param resolver + */ + public CategoryTemplateNode(NodeRef nodeRef, ServiceRegistry services, TemplateImageResolver resolver) + { + super(nodeRef, services, resolver); + } + + @Override + public boolean getIsCategory() + { + return true; + } + + /** + * @return all the member of a category + */ + public List getCategoryMembers() + { + if (getIsCategory()) + { + return buildTemplateNodeList(services.getCategoryService().getChildren(getNodeRef(), + CategoryService.Mode.MEMBERS, CategoryService.Depth.ANY)); + } + else + { + return Collections.emptyList(); + } + } + + /** + * @return all the subcategories of a category + */ + public List getSubCategories() + { + if (getIsCategory()) + { + return buildCategoryNodeList(services.getCategoryService().getChildren(getNodeRef(), + CategoryService.Mode.SUB_CATEGORIES, CategoryService.Depth.ANY)); + } + else + { + return Collections.emptyList(); + } + } + + /** + * @return members and subcategories of a category + */ + public List getMembersAndSubCategories() + { + if (getIsCategory()) + { + + return buildMixedNodeList(services.getCategoryService().getChildren(getNodeRef(), CategoryService.Mode.ALL, + CategoryService.Depth.ANY)); + } + else + { + return Collections.emptyList(); + } + } + + /** + * @return all the immediate member of a category + */ + public List getImmediateCategoryMembers() + { + if (getIsCategory()) + { + return buildTemplateNodeList(services.getCategoryService().getChildren(getNodeRef(), + CategoryService.Mode.MEMBERS, CategoryService.Depth.IMMEDIATE)); + } + else + { + return Collections.emptyList(); + } + } + + /** + * @return all the immediate subcategories of a category + */ + public List getImmediateSubCategories() + { + if (getIsCategory()) + { + return buildCategoryNodeList(services.getCategoryService().getChildren(getNodeRef(), + CategoryService.Mode.SUB_CATEGORIES, CategoryService.Depth.IMMEDIATE)); + } + else + { + return Collections.emptyList(); + } + } + + /** + * @return immediate members and subcategories of a category + */ + public List getImmediateMembersAndSubCategories() + { + if (getIsCategory()) + { + return buildMixedNodeList(services.getCategoryService().getChildren(getNodeRef(), + CategoryService.Mode.ALL, CategoryService.Depth.IMMEDIATE)); + } + else + { + return Collections.emptyList(); + } + } + + /** + * Support to build node lists from category service API calls. + * + * @param childRefs + * + * @return List of TemplateNode + */ + private List buildTemplateNodeList(Collection childRefs) + { + List answer = new ArrayList(childRefs.size()); + for (ChildAssociationRef ref : childRefs) + { + // create our Node representation from the NodeRef + TemplateNode child = new TemplateNode(ref.getChildRef(), this.services, this.imageResolver); + answer.add(child); + } + return answer; + } + + private List buildCategoryNodeList(Collection childRefs) + { + List answer = new ArrayList(childRefs.size()); + for (ChildAssociationRef ref : childRefs) + { + // create our Node representation from the NodeRef + CategoryTemplateNode child = new CategoryTemplateNode(ref.getChildRef(), this.services, this.imageResolver); + answer.add(child); + } + return answer; + } + + private List buildMixedNodeList(Collection cars) + { + List nodes = new ArrayList(cars.size()); + int i = 0; + for (ChildAssociationRef car : cars) + { + QName type = services.getNodeService().getType(car.getChildRef()); + if (services.getDictionaryService().isSubClass(type, ContentModel.TYPE_CATEGORY)) + { + nodes.add(new CategoryTemplateNode(car.getChildRef(), this.services, this.imageResolver)); + } + else + { + nodes.add(new TemplateNode(car.getChildRef(), this.services, this.imageResolver)); + } + } + return nodes; + } +} diff --git a/source/java/org/alfresco/repo/jscript/Classification.java b/source/java/org/alfresco/repo/jscript/Classification.java new file mode 100644 index 0000000000..c093bf630d --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/Classification.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.jscript; + +import java.util.Collection; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.alfresco.service.cmr.search.CategoryService; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.mozilla.javascript.Scriptable; + +/** + * Support class for finding categories, finding root nodes for categories and creating root categories. + * + * @author Andy Hind + */ +public final class Classification implements Scopeable +{ + @SuppressWarnings("unused") + private Scriptable scope; + + private ServiceRegistry services; + + @SuppressWarnings("unused") + private TemplateImageResolver imageResolver; + + private StoreRef storeRef; + + public Classification(ServiceRegistry services, StoreRef storeRef, TemplateImageResolver imageResolver) + { + this.services = services; + this.imageResolver = imageResolver; + this.storeRef = storeRef; + } + + /** + * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) + */ + public void setScope(Scriptable scope) + { + this.scope = scope; + } + + /** + * Find all the category nodes in a given classification. + * + * @param aspect + * @return + */ + public CategoryNode[] getAllCategoryNodes(String aspect) + { + return buildCategoryNodes(services.getCategoryService().getCategories(storeRef, createQName(aspect), + CategoryService.Depth.ANY)); + } + + /** + * Get all the aspects that define a classification. + * + * @return + */ + public String[] getAllClassificationAspects() + { + Collection aspects = services.getCategoryService().getClassificationAspects(); + String[] answer = new String[aspects.size()]; + int i = 0; + for (QName qname : aspects) + { + answer[i++] = qname.toPrefixString(this.services.getNamespaceService()); + } + return answer; + } + + public String[] jsGet_allClassificationAspects() + { + return getAllClassificationAspects(); + } + + /** + * Create a root category in a classification. + * + * @param aspect + * @param name + */ + public void createRootCategory(String aspect, String name) + { + services.getCategoryService().createRootCategory(storeRef, createQName(aspect), name); + } + + /** + * Get the root categories in a classification. + * + * @param aspect + * @return + */ + public CategoryNode[] getRootCategories(String aspect) + { + return buildCategoryNodes(services.getCategoryService().getRootCategories(storeRef, createQName(aspect))); + } + + private CategoryNode[] buildCategoryNodes(Collection cars) + { + CategoryNode[] categoryNodes = new CategoryNode[cars.size()]; + int i = 0; + for (ChildAssociationRef car : cars) + { + categoryNodes[i++] = new CategoryNode(car.getChildRef(), this.services, this.imageResolver, this.scope); + } + return categoryNodes; + } + + private QName createQName(String s) + { + QName qname; + if (s.indexOf(QName.NAMESPACE_BEGIN) != -1) + { + qname = QName.createQName(s); + } + else + { + qname = QName.createQName(s, this.services.getNamespaceService()); + } + return qname; + } + +} diff --git a/source/java/org/alfresco/repo/jscript/Node.java b/source/java/org/alfresco/repo/jscript/Node.java index daeb82ed3f..5d5195833c 100644 --- a/source/java/org/alfresco/repo/jscript/Node.java +++ b/source/java/org/alfresco/repo/jscript/Node.java @@ -92,7 +92,7 @@ public class Node implements Serializable, Scopeable private final static String FOLDER_BROWSE_URL = "/navigate/browse/{0}/{1}/{2}"; /** Root scope for this object */ - private Scriptable scope; + protected Scriptable scope; /** Node Value Converter */ private NodeValueConverter converter = null; @@ -110,18 +110,17 @@ public class Node implements Serializable, Scopeable private Node[] children = null; /** The properties of this node */ private ScriptableQNameMap properties = null; - private ServiceRegistry services = null; + protected ServiceRegistry services = null; private NodeService nodeService = null; private Boolean isDocument = null; private Boolean isContainer = null; private String displayPath = null; - private TemplateImageResolver imageResolver = null; + protected TemplateImageResolver imageResolver = null; private Node parent = null; private ChildAssociationRef primaryParentAssoc = null; // NOTE: see the reset() method when adding new cached members! - // ------------------------------------------------------------------------------ // Construction @@ -428,7 +427,7 @@ public class Node implements Serializable, Scopeable /** * @return true if this Node is a container (i.e. a folder) */ - public boolean isContainer() + public boolean getIsContainer() { if (isContainer == null) { @@ -442,13 +441,13 @@ public class Node implements Serializable, Scopeable public boolean jsGet_isContainer() { - return isContainer(); + return getIsContainer(); } /** * @return true if this Node is a Document (i.e. with content) */ - public boolean isDocument() + public boolean getIsDocument() { if (isDocument == null) { @@ -461,7 +460,21 @@ public class Node implements Serializable, Scopeable public boolean jsGet_isDocument() { - return isDocument(); + return getIsDocument(); + } + + /** + * @return true if the Node is a Category + */ + public boolean getIsCategory() + { + // this valid is overriden by the CategoryNode sub-class + return false; + } + + public boolean jsGet_isCategory() + { + return getIsCategory(); } /** @@ -554,7 +567,7 @@ public class Node implements Serializable, Scopeable { if (this.imageResolver != null) { - if (isDocument()) + if (getIsDocument()) { return this.imageResolver.resolveImagePathForName(getName(), true); } @@ -581,7 +594,7 @@ public class Node implements Serializable, Scopeable { if (this.imageResolver != null) { - if (isDocument()) + if (getIsDocument()) { return this.imageResolver.resolveImagePathForName(getName(), false); } @@ -727,7 +740,7 @@ public class Node implements Serializable, Scopeable */ public String getUrl() { - if (isDocument() == true) + if (getIsDocument() == true) { try { @@ -890,7 +903,8 @@ public class Node implements Serializable, Scopeable this.services.getPermissionService().deletePermission(this.nodeRef, authority, permission); } - // ------------- + + // ------------------------------------------------------------------------------ // Ownership API /** @@ -1145,7 +1159,7 @@ public class Node implements Serializable, Scopeable { if (destination != null) { - NodeRef copyRef = this.services.getCopyService().copy( + NodeRef copyRef = this.services.getCopyService().copyAndRename( this.nodeRef, destination.getNodeRef(), ContentModel.ASSOC_CONTAINS, @@ -1632,7 +1646,7 @@ public class Node implements Serializable, Scopeable this.imageResolver); // add the current node as either the document/space as appropriate - if (this.isDocument()) + if (this.getIsDocument()) { model.put("document", new TemplateNode(this.nodeRef, this.services, this.imageResolver)); model.put("space", new TemplateNode(getPrimaryParentAssoc().getParentRef(), this.services, this.imageResolver)); @@ -1724,7 +1738,7 @@ public class Node implements Serializable, Scopeable /** * Reset the Node cached state */ - private void reset() + public void reset() { this.name = null; this.type = null; diff --git a/source/java/org/alfresco/repo/jscript/RhinoScriptService.java b/source/java/org/alfresco/repo/jscript/RhinoScriptService.java index 9e501435c3..e92b887c91 100644 --- a/source/java/org/alfresco/repo/jscript/RhinoScriptService.java +++ b/source/java/org/alfresco/repo/jscript/RhinoScriptService.java @@ -21,7 +21,9 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.alfresco.error.AlfrescoRuntimeException; @@ -30,6 +32,7 @@ import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.ScriptException; +import org.alfresco.service.cmr.repository.ScriptImplementation; import org.alfresco.service.cmr.repository.ScriptService; import org.alfresco.service.cmr.repository.TemplateImageResolver; import org.alfresco.service.namespace.QName; @@ -51,6 +54,8 @@ public class RhinoScriptService implements ScriptService /** Repository Service Registry */ private ServiceRegistry services; + /** List of global scripts */ + private List globalScripts = new ArrayList(5); /** * Set the Service Registry @@ -62,6 +67,14 @@ public class RhinoScriptService implements ScriptService this.services = services; } + /** + * @see org.alfresco.service.cmr.repository.ScriptService#registerScript(java.lang.Object) + */ + public void registerScript(ScriptImplementation script) + { + this.globalScripts.add(script); + } + /** * @see org.alfresco.service.cmr.repository.ScriptService#executeScript(java.lang.String, java.util.Map) */ @@ -217,28 +230,28 @@ public class RhinoScriptService implements ScriptService model = new HashMap(); } - // add useful util objects - model.put("actions", new Actions(services)); - model.put("logger", new ScriptLogger()); + // add the global scripts + for (ScriptImplementation script : this.globalScripts) + { + model.put(script.getScriptName(), script); + } // insert supplied object model into root of the default scope + for (String key : model.keySet()) { - for (String key : model.keySet()) + // set the root scope on appropriate objects + // this is used to allow native JS object creation etc. + Object obj = model.get(key); + if (obj instanceof Scopeable) { - // set the root scope on appropriate objects - // this is used to allow native JS object creation etc. - Object obj = model.get(key); - if (obj instanceof Scopeable) - { - ((Scopeable)obj).setScope(scope); - } - - // convert/wrap each object to JavaScript compatible - Object jsObject = Context.javaToJS(obj, scope); - - // insert into the root scope ready for access by the script - ScriptableObject.putProperty(scope, key, jsObject); + ((Scopeable)obj).setScope(scope); } + + // convert/wrap each object to JavaScript compatible + Object jsObject = Context.javaToJS(obj, scope); + + // insert into the root scope ready for access by the script + ScriptableObject.putProperty(scope, key, jsObject); } // execute the script @@ -343,6 +356,10 @@ public class RhinoScriptService implements ScriptService model.put("search", new Search(services, companyHome.getStoreRef(), resolver)); + model.put("session", new Session(services, resolver)); + + model.put("classification", new Classification(services, companyHome.getStoreRef(), resolver)); + return model; } } diff --git a/source/java/org/alfresco/repo/jscript/ScriptLogger.java b/source/java/org/alfresco/repo/jscript/ScriptLogger.java index 2f885ff474..29c21d1dfc 100644 --- a/source/java/org/alfresco/repo/jscript/ScriptLogger.java +++ b/source/java/org/alfresco/repo/jscript/ScriptLogger.java @@ -21,7 +21,7 @@ import org.apache.log4j.Logger; /** * @author Kevin Roast */ -public final class ScriptLogger +public final class ScriptLogger extends BaseScriptImplementation { private static final Logger logger = Logger.getLogger(ScriptLogger.class); diff --git a/source/java/org/alfresco/repo/jscript/ScriptUtils.java b/source/java/org/alfresco/repo/jscript/ScriptUtils.java new file mode 100644 index 0000000000..a96d3288e7 --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/ScriptUtils.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.jscript; + +import org.mozilla.javascript.Scriptable; + +/** + * Place for general and miscellenous utility functions not already found in generic JavaScript. + * + * @author Kevin Roast + */ +public final class ScriptUtils extends BaseScriptImplementation implements Scopeable +{ + /** Root scope for this object */ + private Scriptable scope; + + /** + * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) + */ + public void setScope(Scriptable scope) + { + this.scope = scope; + } + + /** + * Function to pad a string with zero '0' characters to the required length + * + * @param s String to pad with leading zero '0' characters + * @param len Length to pad to + * + * @return padded string or the original if already at >=len characters + */ + public String pad(String s, int len) + { + String result = s; + for (int i=0; i<(len - s.length()); i++) + { + result = "0" + result; + } + return result; + } +} diff --git a/source/java/org/alfresco/repo/jscript/Session.java b/source/java/org/alfresco/repo/jscript/Session.java new file mode 100644 index 0000000000..de54dfab33 --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/Session.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.jscript; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.mozilla.javascript.Scriptable; + +/** + * Support object for session level properties etc. + *

    + * Provides access to the user's authentication ticket. + * + * @author Andy Hind + */ +public class Session implements Scopeable +{ + + @SuppressWarnings("unused") + private static Log logger = LogFactory.getLog(Session.class); + + @SuppressWarnings("unused") + private Scriptable scope; + + private ServiceRegistry services; + + @SuppressWarnings("unused") + private TemplateImageResolver imageResolver; + + public Session(ServiceRegistry services, TemplateImageResolver imageResolver) + { + this.services = services; + this.imageResolver = imageResolver; + } + + /** + * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) + */ + public void setScope(Scriptable scope) + { + this.scope = scope; + } + + /** + * Get the user's authentication ticket. + * + * @return + */ + public String getTicket() + { + return services.getAuthenticationService().getCurrentTicket(); + } + + /** + * Expose the user's authentication ticket as JavaScipt property. + * + * @return + */ + public String jsGet_ticket() + { + return getTicket(); + } +} diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java index 6d5eff338d..950571e8c6 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java @@ -503,7 +503,7 @@ public class FileFolderServiceImpl implements FileFolderService */ public FileInfo rename(NodeRef sourceNodeRef, String newName) throws FileExistsException, FileNotFoundException { - return move(sourceNodeRef, null, newName); + return moveOrCopy(sourceNodeRef, null, newName, true); } /** @@ -815,12 +815,25 @@ public class FileFolderServiceImpl implements FileFolderService for (int i = 0; i < folderCount; i++) { String pathElement = pathElements.get(i); - FileInfo pathElementInfo = getPathElementInfo(currentPath, rootNodeRef, parentNodeRef, pathElement, true); - parentNodeRef = pathElementInfo.getNodeRef(); + NodeRef folderNodeRef = searchSimple(parentNodeRef, pathElement); + if (folderNodeRef == null) + { + StringBuilder sb = new StringBuilder(128); + sb.append("Folder not found: " + currentPath); + throw new FileNotFoundException(sb.toString()); + } + parentNodeRef = folderNodeRef; } // we have resolved the folder path - resolve the last component String pathElement = pathElements.get(pathElements.size() - 1); - FileInfo result = getPathElementInfo(currentPath, rootNodeRef, parentNodeRef, pathElement, false); + NodeRef fileNodeRef = searchSimple(parentNodeRef, pathElement); + if (fileNodeRef == null) + { + StringBuilder sb = new StringBuilder(128); + sb.append("File not found: " + currentPath); + throw new FileNotFoundException(sb.toString()); + } + FileInfo result = getFileInfo(fileNodeRef); // found it if (logger.isDebugEnabled()) { @@ -831,42 +844,6 @@ public class FileFolderServiceImpl implements FileFolderService } return result; } - - /** - * Helper method to dig down a level for a node based on name - */ - private FileInfo getPathElementInfo( - StringBuilder currentPath, - NodeRef rootNodeRef, - NodeRef parentNodeRef, - String pathElement, - boolean folderOnly) throws FileNotFoundException - { - currentPath.append("/").append(pathElement); - - boolean includeFiles = (folderOnly ? false : true); - List pathElementInfos = search(parentNodeRef, pathElement, includeFiles, true, false); - // check - if (pathElementInfos.size() == 0) - { - StringBuilder sb = new StringBuilder(128); - sb.append(folderOnly ? "Folder" : "File or folder").append(" not found: \n") - .append(" root: ").append(rootNodeRef).append("\n") - .append(" path: ").append(currentPath); - throw new FileNotFoundException(sb.toString()); - } - else if (pathElementInfos.size() > 1) - { - // we have detected a duplicate name - warn, but allow - StringBuilder sb = new StringBuilder(128); - sb.append("Duplicate file or folder found: \n") - .append(" root: ").append(rootNodeRef).append("\n") - .append(" path: ").append(currentPath); - logger.warn(sb); - } - FileInfo pathElementInfo = pathElementInfos.get(0); - return pathElementInfo; - } public FileInfo getFileInfo(NodeRef nodeRef) { diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java index b92cc1f17e..f71b5c1de2 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java @@ -1,1820 +1,1820 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.repo.node.db; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Stack; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.domain.ChildAssoc; -import org.alfresco.repo.domain.Node; -import org.alfresco.repo.domain.NodeAssoc; -import org.alfresco.repo.domain.NodeStatus; -import org.alfresco.repo.domain.PropertyValue; -import org.alfresco.repo.domain.Store; -import org.alfresco.repo.node.AbstractNodeServiceImpl; -import org.alfresco.repo.node.StoreArchiveMap; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.service.cmr.dictionary.AspectDefinition; -import org.alfresco.service.cmr.dictionary.AssociationDefinition; -import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition; -import org.alfresco.service.cmr.dictionary.ClassDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.dictionary.InvalidAspectException; -import org.alfresco.service.cmr.dictionary.InvalidTypeException; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.cmr.dictionary.TypeDefinition; -import org.alfresco.service.cmr.repository.AssociationExistsException; -import org.alfresco.service.cmr.repository.AssociationRef; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.CyclicChildRelationshipException; -import org.alfresco.service.cmr.repository.InvalidChildAssociationRefException; -import org.alfresco.service.cmr.repository.InvalidNodeRefException; -import org.alfresco.service.cmr.repository.InvalidStoreRefException; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.Path; -import org.alfresco.service.cmr.repository.StoreExistsException; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.repository.NodeRef.Status; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.QNamePattern; -import org.alfresco.util.ParameterCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.util.Assert; - -/** - * Node service using database persistence layer to fulfill functionality - * - * @author Derek Hulley - */ -public class DbNodeServiceImpl extends AbstractNodeServiceImpl -{ - private static Log logger = LogFactory.getLog(DbNodeServiceImpl.class); - private static Log loggerPaths = LogFactory.getLog(DbNodeServiceImpl.class.getName() + ".paths"); - - private NodeDaoService nodeDaoService; - private StoreArchiveMap storeArchiveMap; - private NodeService avmNodeService; - - public DbNodeServiceImpl() - { - storeArchiveMap = new StoreArchiveMap(); // in case it is not set - } - - public void setNodeDaoService(NodeDaoService nodeDaoService) - { - this.nodeDaoService = nodeDaoService; - } - - public void setStoreArchiveMap(StoreArchiveMap storeArchiveMap) - { - this.storeArchiveMap = storeArchiveMap; - } - - public void setAvmNodeService(NodeService avmNodeService) - { - this.avmNodeService = avmNodeService; - } - - /** - * Performs a null-safe get of the node - * - * @param nodeRef the node to retrieve - * @return Returns the node entity (never null) - * @throws InvalidNodeRefException if the referenced node could not be found - */ - private Node getNodeNotNull(NodeRef nodeRef) throws InvalidNodeRefException - { - Node unchecked = nodeDaoService.getNode(nodeRef); - if (unchecked == null) - { - throw new InvalidNodeRefException("Node does not exist: " + nodeRef, nodeRef); - } - return unchecked; - } - - public boolean exists(StoreRef storeRef) - { - Store store = nodeDaoService.getStore(storeRef.getProtocol(), storeRef.getIdentifier()); - boolean exists = (store != null); - // done - return exists; - } - - public boolean exists(NodeRef nodeRef) - { - Node node = nodeDaoService.getNode(nodeRef); - boolean exists = (node != null); - // done - return exists; - } - - public Status getNodeStatus(NodeRef nodeRef) - { - NodeStatus nodeStatus = nodeDaoService.getNodeStatus(nodeRef, false); - if (nodeStatus == null) // node never existed - { - return null; - } - else - { - return new NodeRef.Status( - nodeStatus.getTransaction().getChangeTxnId(), - nodeStatus.isDeleted()); - } - } - - /** - * @see NodeDaoService#getStores() - */ - public List getStores() - { - List stores = nodeDaoService.getStores(); - List storeRefs = new ArrayList(stores.size()); - for (Store store : stores) - { - storeRefs.add(store.getStoreRef()); - } - // Now get the AVMStores. - List avmStores = avmNodeService.getStores(); - storeRefs.addAll(avmStores); - // Return them all. - return storeRefs; - } - - /** - * Defers to the typed service - * @see StoreDaoService#createWorkspace(String) - */ - public StoreRef createStore(String protocol, String identifier) - { - StoreRef storeRef = new StoreRef(protocol, identifier); - // check that the store does not already exist - Store store = nodeDaoService.getStore(protocol, identifier); - if (store != null) - { - throw new StoreExistsException("Unable to create a store that already exists: " + storeRef, storeRef); - } - - // invoke policies - invokeBeforeCreateStore(ContentModel.TYPE_STOREROOT, storeRef); - - // create a new one - store = nodeDaoService.createStore(protocol, identifier); - // get the root node - Node rootNode = store.getRootNode(); - // assign the root aspect - this is expected of all roots, even store roots - addAspect(rootNode.getNodeRef(), - ContentModel.ASPECT_ROOT, - Collections.emptyMap()); - - // invoke policies - invokeOnCreateStore(rootNode.getNodeRef()); - - // done - if (!store.getStoreRef().equals(storeRef)) - { - throw new RuntimeException("Incorrect store reference"); - } - return storeRef; - } - - public NodeRef getRootNode(StoreRef storeRef) throws InvalidStoreRefException - { - Store store = nodeDaoService.getStore(storeRef.getProtocol(), storeRef.getIdentifier()); - if (store == null) - { - throw new InvalidStoreRefException("Store does not exist", storeRef); - } - // get the root - Node node = store.getRootNode(); - if (node == null) - { - throw new InvalidStoreRefException("Store does not have a root node", storeRef); - } - NodeRef nodeRef = node.getNodeRef(); - // done - return nodeRef; - } - - /** - * @see #createNode(NodeRef, QName, QName, QName, Map) - */ - public ChildAssociationRef createNode( - NodeRef parentRef, - QName assocTypeQName, - QName assocQName, - QName nodeTypeQName) - { - return this.createNode(parentRef, assocTypeQName, assocQName, nodeTypeQName, null); - } - - /** - * @see org.alfresco.service.cmr.repository.NodeService#createNode(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName, java.util.Map) - */ - public ChildAssociationRef createNode( - NodeRef parentRef, - QName assocTypeQName, - QName assocQName, - QName nodeTypeQName, - Map properties) - { - Assert.notNull(parentRef); - Assert.notNull(assocTypeQName); - Assert.notNull(assocQName); - - // null property map is allowed - if (properties == null) - { - properties = new HashMap(); - } - else - { - // Copy the incomming property map since we may need to modify it later - properties = new HashMap(properties); - } - - // Invoke policy behaviour - invokeBeforeUpdateNode(parentRef); - invokeBeforeCreateNode(parentRef, assocTypeQName, assocQName, nodeTypeQName); - - // get the store that the parent belongs to - StoreRef storeRef = parentRef.getStoreRef(); - Store store = nodeDaoService.getStore(storeRef.getProtocol(), storeRef.getIdentifier()); - if (store == null) - { - throw new RuntimeException("No store found for parent node: " + parentRef); - } - - // check the node type - TypeDefinition nodeTypeDef = dictionaryService.getType(nodeTypeQName); - if (nodeTypeDef == null) - { - throw new InvalidTypeException(nodeTypeQName); - } - - // get/generate an ID for the node - String newId = generateGuid(properties); - - // create the node instance - Node childNode = nodeDaoService.newNode(store, newId, nodeTypeQName); - - // get the parent node - Node parentNode = getNodeNotNull(parentRef); - - // Set the default property values - addDefaultPropertyValues(nodeTypeDef, properties); - - // Add the default aspects to the node - addDefaultAspects(nodeTypeDef, childNode, properties); - - // set the properties - it is a new node so only set properties if there are any - Map propertiesBefore = getPropertiesImpl(childNode); - Map propertiesAfter = null; - if (properties.size() > 0) - { - propertiesAfter = setPropertiesImpl(childNode, properties); - } - - // create the association - ChildAssoc childAssoc = nodeDaoService.newChildAssoc( - parentNode, - childNode, - true, - assocTypeQName, - assocQName); - setChildUniqueName(childNode); // ensure uniqueness - ChildAssociationRef childAssocRef = childAssoc.getChildAssocRef(); - - // Invoke policy behaviour - invokeOnCreateNode(childAssocRef); - invokeOnUpdateNode(parentRef); - if (propertiesAfter != null) - { - invokeOnUpdateProperties(childAssocRef.getChildRef(), propertiesBefore, propertiesAfter); - } - - // done - return childAssocRef; - } - - /** - * Add the default aspects to a given node - * - * @param nodeTypeDef - */ - private void addDefaultAspects(ClassDefinition classDefinition, Node node, Map properties) - { - NodeRef nodeRef = node.getNodeRef(); - - // get the mandatory aspects for the node type - List defaultAspectDefs = classDefinition.getDefaultAspects(); - - // add all the aspects to the node - Set nodeAspects = node.getAspects(); - for (AspectDefinition defaultAspectDef : defaultAspectDefs) - { - invokeBeforeAddAspect(nodeRef, defaultAspectDef.getName()); - nodeAspects.add(defaultAspectDef.getName()); - addDefaultPropertyValues(defaultAspectDef, properties); - invokeOnAddAspect(nodeRef, defaultAspectDef.getName()); - - // Now add any default aspects for this aspect - addDefaultAspects(defaultAspectDef, node, properties); - } - } - - /** - * Drops the old primary association and creates a new one - */ - public ChildAssociationRef moveNode( - NodeRef nodeToMoveRef, - NodeRef newParentRef, - QName assocTypeQName, - QName assocQName) - throws InvalidNodeRefException - { - Assert.notNull(nodeToMoveRef); - Assert.notNull(newParentRef); - Assert.notNull(assocTypeQName); - Assert.notNull(assocQName); - - // check the node references - Node nodeToMove = getNodeNotNull(nodeToMoveRef); - Node newParentNode = getNodeNotNull(newParentRef); - // get the primary parent assoc - ChildAssoc oldAssoc = nodeDaoService.getPrimaryParentAssoc(nodeToMove); - ChildAssociationRef oldAssocRef = oldAssoc.getChildAssocRef(); - // get the old parent - Node oldParentNode = oldAssoc.getParent(); - - boolean movingStore = !nodeToMoveRef.getStoreRef().equals(newParentRef.getStoreRef()); - - // data needed for policy invocation - QName nodeToMoveTypeQName = nodeToMove.getTypeQName(); - Set nodeToMoveAspects = nodeToMove.getAspects(); - - // Invoke policy behaviour - if (movingStore) - { - invokeBeforeDeleteNode(nodeToMoveRef); - invokeBeforeCreateNode(newParentRef, assocTypeQName, assocQName, nodeToMoveTypeQName); - } - else - { - invokeBeforeDeleteChildAssociation(oldAssocRef); - invokeBeforeCreateChildAssociation(newParentRef, nodeToMoveRef, assocTypeQName, assocQName); - invokeBeforeUpdateNode(oldParentNode.getNodeRef()); // old parent will be updated - invokeBeforeUpdateNode(newParentRef); // new parent ditto - } - - // remove the child assoc from the old parent - // don't cascade as we will still need the node afterwards - nodeDaoService.deleteChildAssoc(oldAssoc, false); - - // create a new assoc - ChildAssoc newAssoc = nodeDaoService.newChildAssoc( - newParentNode, - nodeToMove, - true, - assocTypeQName, - assocQName); - setChildUniqueName(nodeToMove); // ensure uniqueness - ChildAssociationRef newAssocRef = newAssoc.getChildAssocRef(); - - // If the node is moving stores, then drag the node hierarchy with it - if (movingStore) - { - // do the move - Store newStore = newParentNode.getStore(); - moveNodeToStore(nodeToMove, newStore); - // the node reference will have changed too - nodeToMoveRef = nodeToMove.getNodeRef(); - } - - // check that no cyclic relationships have been created - getPaths(nodeToMoveRef, false); - - // invoke policy behaviour - if (movingStore) - { - // TODO for now indicate that the node has been archived to prevent the version history from being removed - // in the future a onMove policy could be added and remove the need for onDelete and onCreate to be fired here - invokeOnDeleteNode(oldAssocRef, nodeToMoveTypeQName, nodeToMoveAspects, true); - invokeOnCreateNode(newAssoc.getChildAssocRef()); - } - else - { - invokeOnCreateChildAssociation(newAssoc.getChildAssocRef()); - invokeOnDeleteChildAssociation(oldAssoc.getChildAssocRef()); - invokeOnUpdateNode(oldParentNode.getNodeRef()); - invokeOnUpdateNode(newParentRef); - } - invokeOnMoveNode(oldAssocRef, newAssocRef); - - // update the node status - nodeDaoService.recordChangeId(nodeToMoveRef); - - // done - return newAssoc.getChildAssocRef(); - } - - public void setChildAssociationIndex(ChildAssociationRef childAssocRef, int index) - { - // get nodes - Node parentNode = getNodeNotNull(childAssocRef.getParentRef()); - Node childNode = getNodeNotNull(childAssocRef.getChildRef()); - - ChildAssoc assoc = nodeDaoService.getChildAssoc( - parentNode, - childNode, - childAssocRef.getTypeQName(), - childAssocRef.getQName()); - if (assoc == null) - { - throw new InvalidChildAssociationRefException("Unable to set child association index: \n" + - " assoc: " + childAssocRef + "\n" + - " index: " + index, - childAssocRef); - } - // set the index - assoc.setIndex(index); - } - - public QName getType(NodeRef nodeRef) throws InvalidNodeRefException - { - Node node = getNodeNotNull(nodeRef); - return node.getTypeQName(); - } - - /** - * @see org.alfresco.service.cmr.repository.NodeService#setType(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName) - */ - public void setType(NodeRef nodeRef, QName typeQName) throws InvalidNodeRefException - { - // check the node type - TypeDefinition nodeTypeDef = dictionaryService.getType(typeQName); - if (nodeTypeDef == null) - { - throw new InvalidTypeException(typeQName); - } - - // Invoke policies - invokeBeforeUpdateNode(nodeRef); - - // Get the node and set the new type - Node node = getNodeNotNull(nodeRef); - node.setTypeQName(typeQName); - - // Add the default aspects to the node (update the properties with any new default values) - Map properties = this.getPropertiesImpl(node); - addDefaultAspects(nodeTypeDef, node, properties); - this.setProperties(nodeRef, properties); - - // Invoke policies - invokeOnUpdateNode(nodeRef); - } - - /** - * @see Node#getAspects() - */ - public void addAspect( - NodeRef nodeRef, - QName aspectTypeQName, - Map aspectProperties) - throws InvalidNodeRefException, InvalidAspectException - { - // check that the aspect is legal - AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName); - if (aspectDef == null) - { - throw new InvalidAspectException("The aspect is invalid: " + aspectTypeQName, aspectTypeQName); - } - - // Invoke policy behaviours - invokeBeforeUpdateNode(nodeRef); - invokeBeforeAddAspect(nodeRef, aspectTypeQName); - - Node node = getNodeNotNull(nodeRef); - - // attach the properties to the current node properties - Map nodeProperties = getPropertiesImpl(node); - - if (aspectProperties != null) - { - nodeProperties.putAll(aspectProperties); - } - - // Set any default property values that appear on the aspect - addDefaultPropertyValues(aspectDef, nodeProperties); - - // Add any dependant aspect - addDefaultAspects(aspectDef, node, nodeProperties); - - // Set the property values back on the node - setProperties(nodeRef, nodeProperties); - - // physically attach the aspect to the node - if (node.getAspects().add(aspectTypeQName) == true) - { - // Invoke policy behaviours - invokeOnUpdateNode(nodeRef); - invokeOnAddAspect(nodeRef, aspectTypeQName); - - // update the node status - nodeDaoService.recordChangeId(nodeRef); - } - } - - /** - * @see Node#getAspects() - */ - public void removeAspect(NodeRef nodeRef, QName aspectTypeQName) - throws InvalidNodeRefException, InvalidAspectException - { - // Invoke policy behaviours - invokeBeforeUpdateNode(nodeRef); - invokeBeforeRemoveAspect(nodeRef, aspectTypeQName); - - // get the aspect - AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName); - if (aspectDef == null) - { - throw new InvalidAspectException(aspectTypeQName); - } - // get the node - Node node = getNodeNotNull(nodeRef); - - // remove the aspect, if present - boolean removed = node.getAspects().remove(aspectTypeQName); - // if the aspect was present, remove the associated properties - if (removed) - { - Map nodeProperties = node.getProperties(); - Map propertyDefs = aspectDef.getProperties(); - for (QName propertyName : propertyDefs.keySet()) - { - nodeProperties.remove(propertyName); - } - - // Invoke policy behaviours - invokeOnUpdateNode(nodeRef); - invokeOnRemoveAspect(nodeRef, aspectTypeQName); - - // update the node status - nodeDaoService.recordChangeId(nodeRef); - } - } - - /** - * Performs a check on the set of node aspects - * - * @see Node#getAspects() - */ - public boolean hasAspect(NodeRef nodeRef, QName aspectRef) throws InvalidNodeRefException, InvalidAspectException - { - Node node = getNodeNotNull(nodeRef); - Set aspectQNames = node.getAspects(); - boolean hasAspect = aspectQNames.contains(aspectRef); - // done - return hasAspect; - } - - public Set getAspects(NodeRef nodeRef) throws InvalidNodeRefException - { - Node node = getNodeNotNull(nodeRef); - Set aspectQNames = node.getAspects(); - // copy the set to ensure initialization - Set ret = new HashSet(aspectQNames.size()); - ret.addAll(aspectQNames); - // done - return ret; - } - - public void deleteNode(NodeRef nodeRef) - { - boolean isArchivedNode = false; - boolean requiresDelete = false; - - // Invoke policy behaviours - invokeBeforeDeleteNode(nodeRef); - - // get the node - Node node = getNodeNotNull(nodeRef); - // get the primary parent-child relationship before it is gone - ChildAssociationRef childAssocRef = getPrimaryParent(nodeRef); - // get type and aspect QNames as they will be unavailable after the delete - QName nodeTypeQName = node.getTypeQName(); - Set nodeAspectQNames = node.getAspects(); - - // check if we need to archive the node - StoreRef archiveStoreRef = null; - if (nodeAspectQNames.contains(ContentModel.ASPECT_TEMPORARY)) - { - // the node has the temporary aspect meaning - // it can not be archived - requiresDelete = true; - isArchivedNode = false; - } - else - { - StoreRef storeRef = nodeRef.getStoreRef(); - archiveStoreRef = storeArchiveMap.getArchiveMap().get(storeRef); - // get the type and check if we need archiving - TypeDefinition typeDef = dictionaryService.getType(node.getTypeQName()); - if (typeDef == null || !typeDef.isArchive() || archiveStoreRef == null) - { - requiresDelete = true; - } - } - - if (requiresDelete) - { - // perform a normal deletion - nodeDaoService.deleteNode(node, true); - isArchivedNode = false; - } - else - { - // archive it - archiveNode(nodeRef, archiveStoreRef); - isArchivedNode = true; - } - - // Invoke policy behaviours - invokeOnDeleteNode(childAssocRef, nodeTypeQName, nodeAspectQNames, isArchivedNode); - } - - public ChildAssociationRef addChild(NodeRef parentRef, NodeRef childRef, QName assocTypeQName, QName assocQName) - { - // Invoke policy behaviours - invokeBeforeUpdateNode(parentRef); - invokeBeforeCreateChildAssociation(parentRef, childRef, assocTypeQName, assocQName); - - // get the parent node and ensure that it is a container node - Node parentNode = getNodeNotNull(parentRef); - // get the child node - Node childNode = getNodeNotNull(childRef); - // make the association - ChildAssoc assoc = nodeDaoService.newChildAssoc( - parentNode, - childNode, - false, - assocTypeQName, - assocQName); - // ensure name uniqueness - setChildUniqueName(childNode); - ChildAssociationRef assocRef = assoc.getChildAssocRef(); - NodeRef childNodeRef = assocRef.getChildRef(); - - // check that the child addition of the child has not created a cyclic relationship - // this functionality is provided for free in getPath - getPaths(childNodeRef, false); - - // Invoke policy behaviours - invokeOnCreateChildAssociation(assocRef); - invokeOnUpdateNode(parentRef); - - return assoc.getChildAssocRef(); - } - - public void removeChild(NodeRef parentRef, NodeRef childRef) throws InvalidNodeRefException - { - Node parentNode = getNodeNotNull(parentRef); - Node childNode = getNodeNotNull(childRef); - Long childNodeId = childNode.getId(); - - // get all the child assocs - ChildAssociationRef primaryAssocRef = null; - Collection assocs = nodeDaoService.getChildAssocs(parentNode); - assocs = new HashSet(assocs); // copy set as we will be modifying it - for (ChildAssoc assoc : assocs) - { - if (!assoc.getChild().getId().equals(childNodeId)) - { - continue; // not a matching association - } - ChildAssociationRef assocRef = assoc.getChildAssocRef(); - // Is this a primary association? - if (assoc.getIsPrimary()) - { - // keep the primary associaton for last - primaryAssocRef = assocRef; - } - else - { - // delete the association instance - it is not primary - invokeBeforeDeleteChildAssociation(assocRef); - nodeDaoService.deleteChildAssoc(assoc, true); // cascade - invokeOnDeleteChildAssociation(assocRef); - } - } - // remove the child if the primary association was a match - if (primaryAssocRef != null) - { - deleteNode(primaryAssocRef.getChildRef()); - } - - // Invoke policy behaviours - invokeOnUpdateNode(parentRef); - - // done - } - - public Map getProperties(NodeRef nodeRef) throws InvalidNodeRefException - { - Node node = getNodeNotNull(nodeRef); - return getPropertiesImpl(node); - } - - private Map getPropertiesImpl(Node node) throws InvalidNodeRefException - { - NodeRef nodeRef = node.getNodeRef(); - - Map nodeProperties = node.getProperties(); - Map ret = new HashMap(nodeProperties.size()); - // copy values - for (Map.Entry entry: nodeProperties.entrySet()) - { - QName propertyQName = entry.getKey(); - PropertyValue propertyValue = entry.getValue(); - // get the property definition - PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); - // convert to the correct type - Serializable value = makeSerializableValue(propertyDef, propertyValue); - // copy across - ret.put(propertyQName, value); - } - // spoof referencable properties - addReferencableProperties(nodeRef, node.getId(), ret); - // done - return ret; - } - - public Serializable getProperty(NodeRef nodeRef, QName qname) throws InvalidNodeRefException - { - // spoof referencable properties - if (qname.equals(ContentModel.PROP_STORE_PROTOCOL)) - { - return nodeRef.getStoreRef().getProtocol(); - } - else if (qname.equals(ContentModel.PROP_STORE_IDENTIFIER)) - { - return nodeRef.getStoreRef().getIdentifier(); - } - else if (qname.equals(ContentModel.PROP_NODE_UUID)) - { - return nodeRef.getId(); - } - - // get the property from the node - Node node = getNodeNotNull(nodeRef); - - if (qname.equals(ContentModel.PROP_NODE_DBID)) - { - return node.getId(); - } - - Map properties = node.getProperties(); - PropertyValue propertyValue = properties.get(qname); - - // get the property definition - PropertyDefinition propertyDef = dictionaryService.getProperty(qname); - // convert to the correct type - Serializable value = makeSerializableValue(propertyDef, propertyValue); - // done - return value; - } - - /** - * Ensures that all required properties are present on the node and copies the - * property values to the Node. - *

    - * To remove a property, remove it from the map before calling this method. - * Null-valued properties are allowed. - *

    - * If any of the values are null, a marker object is put in to mimic nulls. They will be turned back into - * a real nulls when the properties are requested again. - * - * @see Node#getProperties() - */ - public void setProperties(NodeRef nodeRef, Map properties) throws InvalidNodeRefException - { - Node node = getNodeNotNull(nodeRef); - - // Invoke policy behaviours - invokeBeforeUpdateNode(nodeRef); - - // Do the set properties - Map propertiesBefore = getPropertiesImpl(node); - Map propertiesAfter = setPropertiesImpl(node, properties); - - setChildUniqueName(node); // ensure uniqueness - - // Invoke policy behaviours - invokeOnUpdateNode(nodeRef); - invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter); - } - - /** - * Does the work of setting the property values. Returns a map containing the state of the properties after the set - * operation is complete. - * - * @param node the node - * @param properties the map of property values - * @return the map of property values after the set operation is complete - * @throws InvalidNodeRefException - */ - private Map setPropertiesImpl(Node node, Map properties) throws InvalidNodeRefException - { - ParameterCheck.mandatory("properties", properties); - - // remove referencable properties - removeReferencableProperties(properties); - - // copy properties onto node - Map nodeProperties = node.getProperties(); - nodeProperties.clear(); - - // check the property type and copy the values across - for (QName propertyQName : properties.keySet()) - { - PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); - Serializable value = properties.get(propertyQName); - // get a persistable value - PropertyValue propertyValue = makePropertyValue(propertyDef, value); - nodeProperties.put(propertyQName, propertyValue); - } - - // update the node status - NodeRef nodeRef = node.getNodeRef(); - nodeDaoService.recordChangeId(nodeRef); - - // Return the properties after - return Collections.unmodifiableMap(properties); - } - - /** - * Gets the properties map, sets the value (null is allowed) and checks that the new set - * of properties is valid. - * - * @see DbNodeServiceImpl.NullPropertyValue - */ - public void setProperty(NodeRef nodeRef, QName qname, Serializable value) throws InvalidNodeRefException - { - Assert.notNull(qname); - - // Invoke policy behaviours - invokeBeforeUpdateNode(nodeRef); - - // get the node - Node node = getNodeNotNull(nodeRef); - - // Do the set operation - Map propertiesBefore = getPropertiesImpl(node); - Map propertiesAfter = setPropertyImpl(node, qname, value); - - if (qname.equals(ContentModel.PROP_NAME)) - { - setChildUniqueName(node); // ensure uniqueness - } - - // Invoke policy behaviours - invokeOnUpdateNode(nodeRef); - invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter); - } - - /** - * Does the work of setting a property value. Returns the values of the properties after the set operation is - * complete. - * - * @param node the node - * @param qname the qname of the property - * @param value the value of the property - * @return the values of the properties after the set operation is complete - * @throws InvalidNodeRefException - */ - public Map setPropertyImpl(Node node, QName qname, Serializable value) throws InvalidNodeRefException - { - NodeRef nodeRef = node.getNodeRef(); - - Map properties = node.getProperties(); - PropertyDefinition propertyDef = dictionaryService.getProperty(qname); - // get a persistable value - PropertyValue propertyValue = makePropertyValue(propertyDef, value); - properties.put(qname, propertyValue); - - // update the node status - nodeDaoService.recordChangeId(nodeRef); - - return getPropertiesImpl(node); - } - - /** - * Transforms {@link Node#getParentAssocs()} to a new collection - */ - public Collection getParents(NodeRef nodeRef) throws InvalidNodeRefException - { - Node node = getNodeNotNull(nodeRef); - // get the assocs pointing to it - Collection parentAssocs = node.getParentAssocs(); - // list of results - Collection results = new ArrayList(parentAssocs.size()); - for (ChildAssoc assoc : parentAssocs) - { - // get the parent - Node parentNode = assoc.getParent(); - results.add(parentNode.getNodeRef()); - } - // done - return results; - } - - /** - * Filters out any associations if their qname is not a match to the given pattern. - */ - public List getParentAssocs(NodeRef nodeRef, QNamePattern typeQNamePattern, QNamePattern qnamePattern) - { - Node node = getNodeNotNull(nodeRef); - // get the assocs pointing to it - Collection parentAssocs = node.getParentAssocs(); - // shortcut if there are no assocs - if (parentAssocs.size() == 0) - { - return Collections.emptyList(); - } - // list of results - List results = new ArrayList(parentAssocs.size()); - for (ChildAssoc assoc : parentAssocs) - { - // does the qname match the pattern? - if (!qnamePattern.isMatch(assoc.getQname()) || !typeQNamePattern.isMatch(assoc.getTypeQName())) - { - // no match - ignore - continue; - } - results.add(assoc.getChildAssocRef()); - } - // done - return results; - } - - /** - * Filters out any associations if their qname is not a match to the given pattern. - */ - public List getChildAssocs(NodeRef nodeRef, QNamePattern typeQNamePattern, QNamePattern qnamePattern) - { - Node node = getNodeNotNull(nodeRef); - // get the assocs pointing from it - Collection childAssocRefs = nodeDaoService.getChildAssocRefs(node); - // shortcut if there are no assocs - if (childAssocRefs.size() == 0) - { - return Collections.emptyList(); - } - // sort results - ArrayList orderedList = new ArrayList(childAssocRefs); - Collections.sort(orderedList); - - // list of results - int nthSibling = 0; - Iterator iterator = orderedList.iterator(); - while(iterator.hasNext()) - { - ChildAssociationRef childAssocRef = iterator.next(); - // does the qname match the pattern? - if (!qnamePattern.isMatch(childAssocRef.getQName()) || !typeQNamePattern.isMatch(childAssocRef.getTypeQName())) - { - // no match - remove - iterator.remove(); - } - else - { - childAssocRef.setNthSibling(nthSibling); - nthSibling++; - } - } - // done - return orderedList; - } - - public NodeRef getChildByName(NodeRef nodeRef, QName assocTypeQName, String childName) - { - Node node = getNodeNotNull(nodeRef); - ChildAssoc childAssoc = nodeDaoService.getChildAssoc(node, assocTypeQName, childName); - if (childAssoc != null) - { - return childAssoc.getChild().getNodeRef(); - } - else - { - return null; - } - } - - public ChildAssociationRef getPrimaryParent(NodeRef nodeRef) throws InvalidNodeRefException - { - Node node = getNodeNotNull(nodeRef); - // get the primary parent assoc - ChildAssoc assoc = nodeDaoService.getPrimaryParentAssoc(node); - - // done - the assoc may be null for a root node - ChildAssociationRef assocRef = null; - if (assoc == null) - { - assocRef = new ChildAssociationRef(null, null, null, nodeRef); - } - else - { - assocRef = assoc.getChildAssocRef(); - } - return assocRef; - } - - public AssociationRef createAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName) - throws InvalidNodeRefException, AssociationExistsException - { - // Invoke policy behaviours - invokeBeforeUpdateNode(sourceRef); - - Node sourceNode = getNodeNotNull(sourceRef); - Node targetNode = getNodeNotNull(targetRef); - // see if it exists - NodeAssoc assoc = nodeDaoService.getNodeAssoc(sourceNode, targetNode, assocTypeQName); - if (assoc != null) - { - throw new AssociationExistsException(sourceRef, targetRef, assocTypeQName); - } - // we are sure that the association doesn't exist - make it - assoc = nodeDaoService.newNodeAssoc(sourceNode, targetNode, assocTypeQName); - AssociationRef assocRef = assoc.getNodeAssocRef(); - - // Invoke policy behaviours - invokeOnUpdateNode(sourceRef); - invokeOnCreateAssociation(assocRef); - - return assocRef; - } - - public void removeAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName) - throws InvalidNodeRefException - { - Node sourceNode = getNodeNotNull(sourceRef); - Node targetNode = getNodeNotNull(targetRef); - // get the association - NodeAssoc assoc = nodeDaoService.getNodeAssoc(sourceNode, targetNode, assocTypeQName); - if (assoc == null) - { - // nothing to remove - return; - } - AssociationRef assocRef = assoc.getNodeAssocRef(); - - // Invoke policy behaviours - invokeBeforeUpdateNode(sourceRef); - - // delete it - nodeDaoService.deleteNodeAssoc(assoc); - - // Invoke policy behaviours - invokeOnUpdateNode(sourceRef); - invokeOnDeleteAssociation(assocRef); - } - - public List getTargetAssocs(NodeRef sourceRef, QNamePattern qnamePattern) - { - Node sourceNode = getNodeNotNull(sourceRef); - // get all assocs to target - Collection assocs = nodeDaoService.getTargetNodeAssocs(sourceNode); - List nodeAssocRefs = new ArrayList(assocs.size()); - for (NodeAssoc assoc : assocs) - { - // check qname pattern - if (!qnamePattern.isMatch(assoc.getTypeQName())) - { - continue; // the assoc name doesn't match the pattern given - } - nodeAssocRefs.add(assoc.getNodeAssocRef()); - } - // done - return nodeAssocRefs; - } - - public List getSourceAssocs(NodeRef targetRef, QNamePattern qnamePattern) - { - Node targetNode = getNodeNotNull(targetRef); - // get all assocs to source - Collection assocs = nodeDaoService.getSourceNodeAssocs(targetNode); - List nodeAssocRefs = new ArrayList(assocs.size()); - for (NodeAssoc assoc : assocs) - { - // check qname pattern - if (!qnamePattern.isMatch(assoc.getTypeQName())) - { - continue; // the assoc name doesn't match the pattern given - } - nodeAssocRefs.add(assoc.getNodeAssocRef()); - } - // done - return nodeAssocRefs; - } - - /** - * Recursive method used to build up paths from a given node to the root. - *

    - * Whilst walking up the hierarchy to the root, some nodes may have a root aspect. - * Everytime one of these is encountered, a new path is farmed off, but the method - * continues to walk up the hierarchy. - * - * @param currentNode the node to start from, i.e. the child node to work upwards from - * @param currentPath the path from the current node to the descendent that we started from - * @param completedPaths paths that have reached the root are added to this collection - * @param assocStack the parent-child relationships traversed whilst building the path. - * Used to detected cyclic relationships. - * @param primaryOnly true if only the primary parent association must be traversed. - * If this is true, then the only root is the top level node having no parents. - * @throws CyclicChildRelationshipException - */ - private void prependPaths( - final Node currentNode, - final Path currentPath, - Collection completedPaths, - Stack assocStack, - boolean primaryOnly) - throws CyclicChildRelationshipException - { - NodeRef currentNodeRef = currentNode.getNodeRef(); - // get the parent associations of the given node - Collection parentAssocs = currentNode.getParentAssocs(); - // does the node have parents - boolean hasParents = parentAssocs.size() > 0; - // does the current node have a root aspect? - boolean isRoot = hasAspect(currentNodeRef, ContentModel.ASPECT_ROOT); - boolean isStoreRoot = currentNode.getTypeQName().equals(ContentModel.TYPE_STOREROOT); - - // look for a root. If we only want the primary root, then ignore all but the top-level root. - if (isRoot && !(primaryOnly && hasParents)) // exclude primary search with parents present - { - // create a one-sided assoc ref for the root node and prepend to the stack - // this effectively spoofs the fact that the current node is not below the root - // - we put this assoc in as the first assoc in the path must be a one-sided - // reference pointing to the root node - ChildAssociationRef assocRef = new ChildAssociationRef( - null, - null, - null, - getRootNode(currentNode.getNodeRef().getStoreRef())); - // create a path to save and add the 'root' assoc - Path pathToSave = new Path(); - Path.ChildAssocElement first = null; - for (Path.Element element: currentPath) - { - if (first == null) - { - first = (Path.ChildAssocElement) element; - } - else - { - pathToSave.append(element); - } - } - if (first != null) - { - // mimic an association that would appear if the current node was below - // the root node - // or if first beneath the root node it will make the real thing - ChildAssociationRef updateAssocRef = new ChildAssociationRef( - isStoreRoot ? ContentModel.ASSOC_CHILDREN : first.getRef().getTypeQName(), - getRootNode(currentNode.getNodeRef().getStoreRef()), - first.getRef().getQName(), - first.getRef().getChildRef()); - Path.Element newFirst = new Path.ChildAssocElement(updateAssocRef); - pathToSave.prepend(newFirst); - } - - Path.Element element = new Path.ChildAssocElement(assocRef); - pathToSave.prepend(element); - - // store the path just built - completedPaths.add(pathToSave); - } - - if (parentAssocs.size() == 0 && !isRoot) - { - throw new RuntimeException("Node without parents does not have root aspect: " + - currentNodeRef); - } - // walk up each parent association - for (ChildAssoc assoc : parentAssocs) - { - // does the association already exist in the stack - if (assocStack.contains(assoc)) - { - // the association was present already - throw new CyclicChildRelationshipException( - "Cyclic parent-child relationship detected: \n" + - " current node: " + currentNode + "\n" + - " current path: " + currentPath + "\n" + - " next assoc: " + assoc, - assoc); - } - // do we consider only primary assocs? - if (primaryOnly && !assoc.getIsPrimary()) - { - continue; - } - // build a path element - NodeRef parentRef = assoc.getParent().getNodeRef(); - QName qname = assoc.getQname(); - NodeRef childRef = assoc.getChild().getNodeRef(); - boolean isPrimary = assoc.getIsPrimary(); - // build a real association reference - ChildAssociationRef assocRef = new ChildAssociationRef(assoc.getTypeQName(), parentRef, qname, childRef, isPrimary, -1); - // Ordering is not important here: We are building distinct paths upwards - Path.Element element = new Path.ChildAssocElement(assocRef); - // create a new path that builds on the current path - Path path = new Path(); - path.append(currentPath); - // prepend element - path.prepend(element); - // get parent node - Node parentNode = assoc.getParent(); - - // push the assoc stack, recurse and pop - assocStack.push(assoc); - prependPaths(parentNode, path, completedPaths, assocStack, primaryOnly); - assocStack.pop(); - } - // done - } - - /** - * @see #getPaths(NodeRef, boolean) - * @see #prependPaths(Node, Path, Collection, Stack, boolean) - */ - public Path getPath(NodeRef nodeRef) throws InvalidNodeRefException - { - List paths = getPaths(nodeRef, true); // checks primary path count - if (paths.size() == 1) - { - return paths.get(0); // we know there is only one - } - throw new RuntimeException("Primary path count not checked"); // checked by getPaths() - } - - /** - * When searching for primaryOnly == true, checks that there is exactly - * one path. - * @see #prependPaths(Node, Path, Collection, Stack, boolean) - */ - public List getPaths(NodeRef nodeRef, boolean primaryOnly) throws InvalidNodeRefException - { - // get the starting node - Node node = getNodeNotNull(nodeRef); - // create storage for the paths - only need 1 bucket if we are looking for the primary path - List paths = new ArrayList(primaryOnly ? 1 : 10); - // create an empty current path to start from - Path currentPath = new Path(); - // create storage for touched associations - Stack assocStack = new Stack(); - // call recursive method to sort it out - prependPaths(node, currentPath, paths, assocStack, primaryOnly); - - // check that for the primary only case we have exactly one path - if (primaryOnly && paths.size() != 1) - { - throw new RuntimeException("Node has " + paths.size() + " primary paths: " + nodeRef); - } - - // done - if (loggerPaths.isDebugEnabled()) - { - StringBuilder sb = new StringBuilder(256); - if (primaryOnly) - { - sb.append("Primary paths"); - } - else - { - sb.append("Paths"); - } - sb.append(" for node ").append(nodeRef); - for (Path path : paths) - { - sb.append("\n").append(" ").append(path); - } - loggerPaths.debug(sb); - } - return paths; - } - - private void archiveNode(NodeRef nodeRef, StoreRef archiveStoreRef) - { - Node node = getNodeNotNull(nodeRef); - ChildAssoc primaryParentAssoc = nodeDaoService.getPrimaryParentAssoc(node); - - // add the aspect - Set aspects = node.getAspects(); - aspects.add(ContentModel.ASPECT_ARCHIVED); - Map properties = node.getProperties(); - PropertyValue archivedByProperty = makePropertyValue( - dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_BY), - AuthenticationUtil.getCurrentUserName()); - properties.put(ContentModel.PROP_ARCHIVED_BY, archivedByProperty); - PropertyValue archivedDateProperty = makePropertyValue( - dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_DATE), - new Date()); - properties.put(ContentModel.PROP_ARCHIVED_DATE, archivedDateProperty); - PropertyValue archivedPrimaryParentNodeRefProperty = makePropertyValue( - dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC), - primaryParentAssoc.getChildAssocRef()); - properties.put(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC, archivedPrimaryParentNodeRefProperty); - PropertyValue originalOwnerProperty = properties.get(ContentModel.PROP_OWNER); - PropertyValue originalCreatorProperty = properties.get(ContentModel.PROP_CREATOR); - if (originalOwnerProperty != null || originalCreatorProperty != null) - { - properties.put( - ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER, - originalOwnerProperty != null ? originalOwnerProperty : originalCreatorProperty); - } - - // change the node ownership - aspects.add(ContentModel.ASPECT_OWNABLE); - PropertyValue newOwnerProperty = makePropertyValue( - dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER), - AuthenticationUtil.getCurrentUserName()); - properties.put(ContentModel.PROP_OWNER, newOwnerProperty); - - // move the node - NodeRef archiveStoreRootNodeRef = getRootNode(archiveStoreRef); - moveNode( - nodeRef, - archiveStoreRootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedItem")); - - // get the IDs of all the node's primary children, including its own - Map nodesById = getNodeHierarchy(node, null); - - // Archive all the associations between the archived nodes and non-archived nodes - for (Node nodeToArchive : nodesById.values()) - { - archiveAssocs(nodeToArchive, nodesById); - } - - // the node reference has changed due to the store move - nodeRef = node.getNodeRef(); - } - - /** - * Performs all the necessary housekeeping involved in changing a node's store. - * This method cascades down through all the primary children of the node as - * well. - * - * @param node the node whose store is changing - * @param store the new store for the node - */ - private void moveNodeToStore(Node node, Store store) - { - // get the IDs of all the node's primary children, including its own - Map nodesById = getNodeHierarchy(node, null); - - // move each node into the archive store - for (Node nodeToMove : nodesById.values()) - { - NodeRef oldNodeRef = nodeToMove.getNodeRef(); - nodeToMove.setStore(store); - NodeRef newNodeRef = nodeToMove.getNodeRef(); - - // update old status - NodeStatus oldNodeStatus = nodeDaoService.getNodeStatus(oldNodeRef, true); - oldNodeStatus.setNode(null); - // create the new status - NodeStatus newNodeStatus = nodeDaoService.getNodeStatus(newNodeRef, true); - newNodeStatus.setNode(nodeToMove); - } - } - - /** - * Fill the map of all primary children below the given node. - * The given node will be added to the map and the method is recursive - * to all primary children. - * - * @param node the start of the hierarchy - * @param nodesById a map of nodes that will be reused as the return value - * @return Returns a map of nodes in the hierarchy keyed by their IDs - */ - private Map getNodeHierarchy(Node node, Map nodesById) - { - if (nodesById == null) - { - nodesById = new HashMap(23); - } - - Long id = node.getId(); - if (nodesById.containsKey(id)) - { - // this ID was already added - circular reference - logger.warn("Circular hierarchy found including node " + id); - return nodesById; - } - // add the node to the map - nodesById.put(id, node); - // recurse into the primary children - Collection childAssocs = nodeDaoService.getChildAssocs(node); - for (ChildAssoc childAssoc : childAssocs) - { - // cascade into primary associations - if (childAssoc.getIsPrimary()) - { - Node primaryChild = childAssoc.getChild(); - nodesById = getNodeHierarchy(primaryChild, nodesById); - } - } - return nodesById; - } - - /** - * Archive all associations to and from the given node, with the - * exception of associations to or from nodes in the given map. - *

    - * Primary parent associations are also ignored. - * - * @param node the node whose associations must be archived - * @param nodesById a map of nodes partaking in the archival process - */ - private void archiveAssocs(Node node, Map nodesById) - { - List childAssocsToDelete = new ArrayList(5); - // child associations - ArrayList archivedChildAssocRefs = new ArrayList(5); - Collection childAssocs = nodeDaoService.getChildAssocs(node); - for (ChildAssoc assoc : childAssocs) - { - Long relatedNodeId = assoc.getChild().getId(); - if (nodesById.containsKey(relatedNodeId)) - { - // a sibling in the archive process - continue; - } - childAssocsToDelete.add(assoc); - archivedChildAssocRefs.add(assoc.getChildAssocRef()); - } - // parent associations - ArrayList archivedParentAssocRefs = new ArrayList(5); - for (ChildAssoc assoc : node.getParentAssocs()) - { - Long relatedNodeId = assoc.getParent().getId(); - if (nodesById.containsKey(relatedNodeId)) - { - // a sibling in the archive process - continue; - } - else if (assoc.getIsPrimary()) - { - // ignore the primary parent as this is handled more specifically - continue; - } - childAssocsToDelete.add(assoc); - archivedParentAssocRefs.add(assoc.getChildAssocRef()); - } - - List nodeAssocsToDelete = new ArrayList(5); - // source associations - ArrayList archivedSourceAssocRefs = new ArrayList(5); - for (NodeAssoc assoc : nodeDaoService.getSourceNodeAssocs(node)) - { - Long relatedNodeId = assoc.getSource().getId(); - if (nodesById.containsKey(relatedNodeId)) - { - // a sibling in the archive process - continue; - } - nodeAssocsToDelete.add(assoc); - archivedSourceAssocRefs.add(assoc.getNodeAssocRef()); - } - // target associations - ArrayList archivedTargetAssocRefs = new ArrayList(5); - for (NodeAssoc assoc : nodeDaoService.getTargetNodeAssocs(node)) - { - Long relatedNodeId = assoc.getTarget().getId(); - if (nodesById.containsKey(relatedNodeId)) - { - // a sibling in the archive process - continue; - } - nodeAssocsToDelete.add(assoc); - archivedTargetAssocRefs.add(assoc.getNodeAssocRef()); - } - // delete child assocs - for (ChildAssoc assoc : childAssocsToDelete) - { - nodeDaoService.deleteChildAssoc(assoc, false); - } - // delete node assocs - for (NodeAssoc assoc : nodeAssocsToDelete) - { - nodeDaoService.deleteNodeAssoc(assoc); - } - - // add archived aspect - node.getAspects().add(ContentModel.ASPECT_ARCHIVED_ASSOCS); - // set properties - Map properties = node.getProperties(); - - if (archivedParentAssocRefs.size() > 0) - { - PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_PARENT_ASSOCS); - PropertyValue propertyValue = makePropertyValue(propertyDef, archivedParentAssocRefs); - properties.put(ContentModel.PROP_ARCHIVED_PARENT_ASSOCS, propertyValue); - } - if (archivedChildAssocRefs.size() > 0) - { - PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_CHILD_ASSOCS); - PropertyValue propertyValue = makePropertyValue(propertyDef, archivedChildAssocRefs); - properties.put(ContentModel.PROP_ARCHIVED_CHILD_ASSOCS, propertyValue); - } - if (archivedSourceAssocRefs.size() > 0) - { - PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS); - PropertyValue propertyValue = makePropertyValue(propertyDef, archivedSourceAssocRefs); - properties.put(ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS, propertyValue); - } - if (archivedTargetAssocRefs.size() > 0) - { - PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_TARGET_ASSOCS); - PropertyValue propertyValue = makePropertyValue(propertyDef, archivedTargetAssocRefs); - properties.put(ContentModel.PROP_ARCHIVED_TARGET_ASSOCS, propertyValue); - } - } - - public NodeRef getStoreArchiveNode(StoreRef storeRef) - { - StoreRef archiveStoreRef = storeArchiveMap.getArchiveMap().get(storeRef); - if (archiveStoreRef == null) - { - // no mapping for the given store - return null; - } - else - { - return getRootNode(archiveStoreRef); - } - } - - public NodeRef restoreNode(NodeRef archivedNodeRef, NodeRef destinationParentNodeRef, QName assocTypeQName, QName assocQName) - { - Node archivedNode = getNodeNotNull(archivedNodeRef); - Set aspects = archivedNode.getAspects(); - Map properties = archivedNode.getProperties(); - // the node must be a top-level archive node - if (!aspects.contains(ContentModel.ASPECT_ARCHIVED)) - { - throw new AlfrescoRuntimeException("The node to archive is not an archive node"); - } - ChildAssociationRef originalPrimaryParentAssocRef = (ChildAssociationRef) makeSerializableValue( - dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC), - properties.get(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC)); - PropertyValue originalOwnerProperty = properties.get(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER); - // remove the aspect archived aspect - aspects.remove(ContentModel.ASPECT_ARCHIVED); - properties.remove(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC); - properties.remove(ContentModel.PROP_ARCHIVED_BY); - properties.remove(ContentModel.PROP_ARCHIVED_DATE); - properties.remove(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER); - - // restore the original ownership - if (originalOwnerProperty != null) - { - aspects.add(ContentModel.ASPECT_OWNABLE); - properties.put(ContentModel.PROP_OWNER, originalOwnerProperty); - } - - if (destinationParentNodeRef == null) - { - // we must restore to the original location - destinationParentNodeRef = originalPrimaryParentAssocRef.getParentRef(); - } - // check the associations - if (assocTypeQName == null) - { - assocTypeQName = originalPrimaryParentAssocRef.getTypeQName(); - } - if (assocQName == null) - { - assocQName = originalPrimaryParentAssocRef.getQName(); - } - - // move the node to the target parent, which may or may not be the original parent - moveNode( - archivedNodeRef, - destinationParentNodeRef, - assocTypeQName, - assocQName); - - // get the IDs of all the node's primary children, including its own - Map restoredNodesById = getNodeHierarchy(archivedNode, null); - // Restore the archived associations, if required - for (Node restoredNode : restoredNodesById.values()) - { - restoreAssocs(restoredNode); - } - - // the node reference has changed due to the store move - NodeRef restoredNodeRef = archivedNode.getNodeRef(); - - // done - if (logger.isDebugEnabled()) - { - logger.debug("Restored node: \n" + - " original noderef: " + archivedNodeRef + "\n" + - " restored noderef: " + restoredNodeRef + "\n" + - " new parent: " + destinationParentNodeRef); - } - return restoredNodeRef; - } - - private void restoreAssocs(Node node) - { - NodeRef nodeRef = node.getNodeRef(); - // set properties - Map properties = node.getProperties(); - - // restore parent associations - Collection parentAssocRefs = (Collection) getProperty( - nodeRef, - ContentModel.PROP_ARCHIVED_PARENT_ASSOCS); - if (parentAssocRefs != null) - { - for (ChildAssociationRef assocRef : parentAssocRefs) - { - NodeRef parentNodeRef = assocRef.getParentRef(); - if (!exists(parentNodeRef)) - { - continue; - } - Node parentNode = getNodeNotNull(parentNodeRef); - // get the name to use for the unique child check - QName assocTypeQName = assocRef.getTypeQName(); - nodeDaoService.newChildAssoc( - parentNode, - node, - assocRef.isPrimary(), - assocTypeQName, - assocRef.getQName()); - } - properties.remove(ContentModel.PROP_ARCHIVED_PARENT_ASSOCS); - } - - // make sure that the node name uniqueness is enforced - setChildUniqueName(node); - - // restore child associations - Collection childAssocRefs = (Collection) getProperty( - nodeRef, - ContentModel.PROP_ARCHIVED_CHILD_ASSOCS); - if (childAssocRefs != null) - { - for (ChildAssociationRef assocRef : childAssocRefs) - { - NodeRef childNodeRef = assocRef.getChildRef(); - if (!exists(childNodeRef)) - { - continue; - } - Node childNode = getNodeNotNull(childNodeRef); - QName assocTypeQName = assocRef.getTypeQName(); - // get the name to use for the unique child check - nodeDaoService.newChildAssoc( - node, - childNode, - assocRef.isPrimary(), - assocTypeQName, - assocRef.getQName()); - // ensure that the name uniqueness is enforced for the child node - setChildUniqueName(childNode); - } - properties.remove(ContentModel.PROP_ARCHIVED_CHILD_ASSOCS); - } - // restore source associations - Collection sourceAssocRefs = (Collection) getProperty( - nodeRef, - ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS); - if (sourceAssocRefs != null) - { - for (AssociationRef assocRef : sourceAssocRefs) - { - NodeRef sourceNodeRef = assocRef.getSourceRef(); - if (!exists(sourceNodeRef)) - { - continue; - } - Node sourceNode = getNodeNotNull(sourceNodeRef); - nodeDaoService.newNodeAssoc(sourceNode, node, assocRef.getTypeQName()); - } - properties.remove(ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS); - } - // restore target associations - Collection targetAssocRefs = (Collection) getProperty( - nodeRef, - ContentModel.PROP_ARCHIVED_TARGET_ASSOCS); - if (targetAssocRefs != null) - { - for (AssociationRef assocRef : targetAssocRefs) - { - NodeRef targetNodeRef = assocRef.getTargetRef(); - if (!exists(targetNodeRef)) - { - continue; - } - Node targetNode = getNodeNotNull(targetNodeRef); - nodeDaoService.newNodeAssoc(node, targetNode, assocRef.getTypeQName()); - } - properties.remove(ContentModel.PROP_ARCHIVED_TARGET_ASSOCS); - } - // remove the aspect - node.getAspects().remove(ContentModel.ASPECT_ARCHIVED_ASSOCS); - } - - /** - * Checks the dictionary's definition of the association to assign a unique name to the child node. - * - * @param assocTypeQName the type of the child association - * @param childNode the child node being added. The name will be extracted from it, if necessary. - * @return Returns the value to be put on the child association for uniqueness, or null if - */ - private void setChildUniqueName(Node childNode) - { - // get the name property - Map properties = childNode.getProperties(); - PropertyValue nameValue = properties.get(ContentModel.PROP_NAME); - String useName = null; - if (nameValue == null) - { - // no name has been assigned, so assign the ID of the child node - useName = childNode.getUuid(); - } - else - { - useName = (String) nameValue.getValue(DataTypeDefinition.TEXT); - } - // get all the parent assocs - Collection parentAssocs = childNode.getParentAssocs(); - for (ChildAssoc assoc : parentAssocs) - { - QName assocTypeQName = assoc.getTypeQName(); - AssociationDefinition assocDef = dictionaryService.getAssociation(assocTypeQName); - if (!assocDef.isChild()) - { - throw new DataIntegrityViolationException("Child association has non-child type: " + assoc.getId()); - } - ChildAssociationDefinition childAssocDef = (ChildAssociationDefinition) assocDef; - if (childAssocDef.getDuplicateChildNamesAllowed()) - { - // the name is irrelevant, so it doesn't need to be put into the unique key - nodeDaoService.setChildNameUnique(assoc, null); - } - else - { - nodeDaoService.setChildNameUnique(assoc, useName); - } - } - // done - if (logger.isDebugEnabled()) - { - logger.debug( - "Unique name set for all " + parentAssocs.size() + " parent associations: \n" + - " name: " + useName); - } - } -} +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.node.db; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Stack; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.domain.ChildAssoc; +import org.alfresco.repo.domain.Node; +import org.alfresco.repo.domain.NodeAssoc; +import org.alfresco.repo.domain.NodeStatus; +import org.alfresco.repo.domain.PropertyValue; +import org.alfresco.repo.domain.Store; +import org.alfresco.repo.node.AbstractNodeServiceImpl; +import org.alfresco.repo.node.StoreArchiveMap; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.InvalidAspectException; +import org.alfresco.service.cmr.dictionary.InvalidTypeException; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.AssociationExistsException; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.CyclicChildRelationshipException; +import org.alfresco.service.cmr.repository.InvalidChildAssociationRefException; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.InvalidStoreRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.repository.StoreExistsException; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.NodeRef.Status; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.QNamePattern; +import org.alfresco.util.ParameterCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.util.Assert; + +/** + * Node service using database persistence layer to fulfill functionality + * + * @author Derek Hulley + */ +public class DbNodeServiceImpl extends AbstractNodeServiceImpl +{ + private static Log logger = LogFactory.getLog(DbNodeServiceImpl.class); + private static Log loggerPaths = LogFactory.getLog(DbNodeServiceImpl.class.getName() + ".paths"); + + private NodeDaoService nodeDaoService; + private StoreArchiveMap storeArchiveMap; + private NodeService avmNodeService; + + public DbNodeServiceImpl() + { + storeArchiveMap = new StoreArchiveMap(); // in case it is not set + } + + public void setNodeDaoService(NodeDaoService nodeDaoService) + { + this.nodeDaoService = nodeDaoService; + } + + public void setStoreArchiveMap(StoreArchiveMap storeArchiveMap) + { + this.storeArchiveMap = storeArchiveMap; + } + + public void setAvmNodeService(NodeService avmNodeService) + { + this.avmNodeService = avmNodeService; + } + + /** + * Performs a null-safe get of the node + * + * @param nodeRef the node to retrieve + * @return Returns the node entity (never null) + * @throws InvalidNodeRefException if the referenced node could not be found + */ + private Node getNodeNotNull(NodeRef nodeRef) throws InvalidNodeRefException + { + Node unchecked = nodeDaoService.getNode(nodeRef); + if (unchecked == null) + { + throw new InvalidNodeRefException("Node does not exist: " + nodeRef, nodeRef); + } + return unchecked; + } + + public boolean exists(StoreRef storeRef) + { + Store store = nodeDaoService.getStore(storeRef.getProtocol(), storeRef.getIdentifier()); + boolean exists = (store != null); + // done + return exists; + } + + public boolean exists(NodeRef nodeRef) + { + Node node = nodeDaoService.getNode(nodeRef); + boolean exists = (node != null); + // done + return exists; + } + + public Status getNodeStatus(NodeRef nodeRef) + { + NodeStatus nodeStatus = nodeDaoService.getNodeStatus(nodeRef, false); + if (nodeStatus == null) // node never existed + { + return null; + } + else + { + return new NodeRef.Status( + nodeStatus.getTransaction().getChangeTxnId(), + nodeStatus.isDeleted()); + } + } + + /** + * @see NodeDaoService#getStores() + */ + public List getStores() + { + List stores = nodeDaoService.getStores(); + List storeRefs = new ArrayList(stores.size()); + for (Store store : stores) + { + storeRefs.add(store.getStoreRef()); + } + // Now get the AVMStores. + List avmStores = avmNodeService.getStores(); + storeRefs.addAll(avmStores); + // Return them all. + return storeRefs; + } + + /** + * Defers to the typed service + * @see StoreDaoService#createWorkspace(String) + */ + public StoreRef createStore(String protocol, String identifier) + { + StoreRef storeRef = new StoreRef(protocol, identifier); + // check that the store does not already exist + Store store = nodeDaoService.getStore(protocol, identifier); + if (store != null) + { + throw new StoreExistsException("Unable to create a store that already exists: " + storeRef, storeRef); + } + + // invoke policies + invokeBeforeCreateStore(ContentModel.TYPE_STOREROOT, storeRef); + + // create a new one + store = nodeDaoService.createStore(protocol, identifier); + // get the root node + Node rootNode = store.getRootNode(); + // assign the root aspect - this is expected of all roots, even store roots + addAspect(rootNode.getNodeRef(), + ContentModel.ASPECT_ROOT, + Collections.emptyMap()); + + // invoke policies + invokeOnCreateStore(rootNode.getNodeRef()); + + // done + if (!store.getStoreRef().equals(storeRef)) + { + throw new RuntimeException("Incorrect store reference"); + } + return storeRef; + } + + public NodeRef getRootNode(StoreRef storeRef) throws InvalidStoreRefException + { + Store store = nodeDaoService.getStore(storeRef.getProtocol(), storeRef.getIdentifier()); + if (store == null) + { + throw new InvalidStoreRefException("Store does not exist", storeRef); + } + // get the root + Node node = store.getRootNode(); + if (node == null) + { + throw new InvalidStoreRefException("Store does not have a root node", storeRef); + } + NodeRef nodeRef = node.getNodeRef(); + // done + return nodeRef; + } + + /** + * @see #createNode(NodeRef, QName, QName, QName, Map) + */ + public ChildAssociationRef createNode( + NodeRef parentRef, + QName assocTypeQName, + QName assocQName, + QName nodeTypeQName) + { + return this.createNode(parentRef, assocTypeQName, assocQName, nodeTypeQName, null); + } + + /** + * @see org.alfresco.service.cmr.repository.NodeService#createNode(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName, java.util.Map) + */ + public ChildAssociationRef createNode( + NodeRef parentRef, + QName assocTypeQName, + QName assocQName, + QName nodeTypeQName, + Map properties) + { + Assert.notNull(parentRef); + Assert.notNull(assocTypeQName); + Assert.notNull(assocQName); + + // null property map is allowed + if (properties == null) + { + properties = new HashMap(); + } + else + { + // Copy the incomming property map since we may need to modify it later + properties = new HashMap(properties); + } + + // Invoke policy behaviour + invokeBeforeUpdateNode(parentRef); + invokeBeforeCreateNode(parentRef, assocTypeQName, assocQName, nodeTypeQName); + + // get the store that the parent belongs to + StoreRef storeRef = parentRef.getStoreRef(); + Store store = nodeDaoService.getStore(storeRef.getProtocol(), storeRef.getIdentifier()); + if (store == null) + { + throw new RuntimeException("No store found for parent node: " + parentRef); + } + + // check the node type + TypeDefinition nodeTypeDef = dictionaryService.getType(nodeTypeQName); + if (nodeTypeDef == null) + { + throw new InvalidTypeException(nodeTypeQName); + } + + // get/generate an ID for the node + String newId = generateGuid(properties); + + // create the node instance + Node childNode = nodeDaoService.newNode(store, newId, nodeTypeQName); + + // get the parent node + Node parentNode = getNodeNotNull(parentRef); + + // Set the default property values + addDefaultPropertyValues(nodeTypeDef, properties); + + // Add the default aspects to the node + addDefaultAspects(nodeTypeDef, childNode, properties); + + // set the properties - it is a new node so only set properties if there are any + Map propertiesBefore = getPropertiesImpl(childNode); + Map propertiesAfter = null; + if (properties.size() > 0) + { + propertiesAfter = setPropertiesImpl(childNode, properties); + } + + // create the association + ChildAssoc childAssoc = nodeDaoService.newChildAssoc( + parentNode, + childNode, + true, + assocTypeQName, + assocQName); + setChildUniqueName(childNode); // ensure uniqueness + ChildAssociationRef childAssocRef = childAssoc.getChildAssocRef(); + + // Invoke policy behaviour + invokeOnCreateNode(childAssocRef); + invokeOnUpdateNode(parentRef); + if (propertiesAfter != null) + { + invokeOnUpdateProperties(childAssocRef.getChildRef(), propertiesBefore, propertiesAfter); + } + + // done + return childAssocRef; + } + + /** + * Add the default aspects to a given node + * + * @param nodeTypeDef + */ + private void addDefaultAspects(ClassDefinition classDefinition, Node node, Map properties) + { + NodeRef nodeRef = node.getNodeRef(); + + // get the mandatory aspects for the node type + List defaultAspectDefs = classDefinition.getDefaultAspects(); + + // add all the aspects to the node + Set nodeAspects = node.getAspects(); + for (AspectDefinition defaultAspectDef : defaultAspectDefs) + { + invokeBeforeAddAspect(nodeRef, defaultAspectDef.getName()); + nodeAspects.add(defaultAspectDef.getName()); + addDefaultPropertyValues(defaultAspectDef, properties); + invokeOnAddAspect(nodeRef, defaultAspectDef.getName()); + + // Now add any default aspects for this aspect + addDefaultAspects(defaultAspectDef, node, properties); + } + } + + /** + * Drops the old primary association and creates a new one + */ + public ChildAssociationRef moveNode( + NodeRef nodeToMoveRef, + NodeRef newParentRef, + QName assocTypeQName, + QName assocQName) + throws InvalidNodeRefException + { + Assert.notNull(nodeToMoveRef); + Assert.notNull(newParentRef); + Assert.notNull(assocTypeQName); + Assert.notNull(assocQName); + + // check the node references + Node nodeToMove = getNodeNotNull(nodeToMoveRef); + Node newParentNode = getNodeNotNull(newParentRef); + // get the primary parent assoc + ChildAssoc oldAssoc = nodeDaoService.getPrimaryParentAssoc(nodeToMove); + ChildAssociationRef oldAssocRef = oldAssoc.getChildAssocRef(); + // get the old parent + Node oldParentNode = oldAssoc.getParent(); + + boolean movingStore = !nodeToMoveRef.getStoreRef().equals(newParentRef.getStoreRef()); + + // data needed for policy invocation + QName nodeToMoveTypeQName = nodeToMove.getTypeQName(); + Set nodeToMoveAspects = nodeToMove.getAspects(); + + // Invoke policy behaviour + if (movingStore) + { + invokeBeforeDeleteNode(nodeToMoveRef); + invokeBeforeCreateNode(newParentRef, assocTypeQName, assocQName, nodeToMoveTypeQName); + } + else + { + invokeBeforeDeleteChildAssociation(oldAssocRef); + invokeBeforeCreateChildAssociation(newParentRef, nodeToMoveRef, assocTypeQName, assocQName); + invokeBeforeUpdateNode(oldParentNode.getNodeRef()); // old parent will be updated + invokeBeforeUpdateNode(newParentRef); // new parent ditto + } + + // remove the child assoc from the old parent + // don't cascade as we will still need the node afterwards + nodeDaoService.deleteChildAssoc(oldAssoc, false); + + // create a new assoc + ChildAssoc newAssoc = nodeDaoService.newChildAssoc( + newParentNode, + nodeToMove, + true, + assocTypeQName, + assocQName); + setChildUniqueName(nodeToMove); // ensure uniqueness + ChildAssociationRef newAssocRef = newAssoc.getChildAssocRef(); + + // If the node is moving stores, then drag the node hierarchy with it + if (movingStore) + { + // do the move + Store newStore = newParentNode.getStore(); + moveNodeToStore(nodeToMove, newStore); + // the node reference will have changed too + nodeToMoveRef = nodeToMove.getNodeRef(); + } + + // check that no cyclic relationships have been created + getPaths(nodeToMoveRef, false); + + // invoke policy behaviour + if (movingStore) + { + // TODO for now indicate that the node has been archived to prevent the version history from being removed + // in the future a onMove policy could be added and remove the need for onDelete and onCreate to be fired here + invokeOnDeleteNode(oldAssocRef, nodeToMoveTypeQName, nodeToMoveAspects, true); + invokeOnCreateNode(newAssoc.getChildAssocRef()); + } + else + { + invokeOnCreateChildAssociation(newAssoc.getChildAssocRef()); + invokeOnDeleteChildAssociation(oldAssoc.getChildAssocRef()); + invokeOnUpdateNode(oldParentNode.getNodeRef()); + invokeOnUpdateNode(newParentRef); + } + invokeOnMoveNode(oldAssocRef, newAssocRef); + + // update the node status + nodeDaoService.recordChangeId(nodeToMoveRef); + + // done + return newAssoc.getChildAssocRef(); + } + + public void setChildAssociationIndex(ChildAssociationRef childAssocRef, int index) + { + // get nodes + Node parentNode = getNodeNotNull(childAssocRef.getParentRef()); + Node childNode = getNodeNotNull(childAssocRef.getChildRef()); + + ChildAssoc assoc = nodeDaoService.getChildAssoc( + parentNode, + childNode, + childAssocRef.getTypeQName(), + childAssocRef.getQName()); + if (assoc == null) + { + throw new InvalidChildAssociationRefException("Unable to set child association index: \n" + + " assoc: " + childAssocRef + "\n" + + " index: " + index, + childAssocRef); + } + // set the index + assoc.setIndex(index); + } + + public QName getType(NodeRef nodeRef) throws InvalidNodeRefException + { + Node node = getNodeNotNull(nodeRef); + return node.getTypeQName(); + } + + /** + * @see org.alfresco.service.cmr.repository.NodeService#setType(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName) + */ + public void setType(NodeRef nodeRef, QName typeQName) throws InvalidNodeRefException + { + // check the node type + TypeDefinition nodeTypeDef = dictionaryService.getType(typeQName); + if (nodeTypeDef == null) + { + throw new InvalidTypeException(typeQName); + } + + // Invoke policies + invokeBeforeUpdateNode(nodeRef); + + // Get the node and set the new type + Node node = getNodeNotNull(nodeRef); + node.setTypeQName(typeQName); + + // Add the default aspects to the node (update the properties with any new default values) + Map properties = this.getPropertiesImpl(node); + addDefaultAspects(nodeTypeDef, node, properties); + this.setProperties(nodeRef, properties); + + // Invoke policies + invokeOnUpdateNode(nodeRef); + } + + /** + * @see Node#getAspects() + */ + public void addAspect( + NodeRef nodeRef, + QName aspectTypeQName, + Map aspectProperties) + throws InvalidNodeRefException, InvalidAspectException + { + // check that the aspect is legal + AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName); + if (aspectDef == null) + { + throw new InvalidAspectException("The aspect is invalid: " + aspectTypeQName, aspectTypeQName); + } + + // Invoke policy behaviours + invokeBeforeUpdateNode(nodeRef); + invokeBeforeAddAspect(nodeRef, aspectTypeQName); + + Node node = getNodeNotNull(nodeRef); + + // attach the properties to the current node properties + Map nodeProperties = getPropertiesImpl(node); + + if (aspectProperties != null) + { + nodeProperties.putAll(aspectProperties); + } + + // Set any default property values that appear on the aspect + addDefaultPropertyValues(aspectDef, nodeProperties); + + // Add any dependant aspect + addDefaultAspects(aspectDef, node, nodeProperties); + + // Set the property values back on the node + setProperties(nodeRef, nodeProperties); + + // physically attach the aspect to the node + if (node.getAspects().add(aspectTypeQName) == true) + { + // Invoke policy behaviours + invokeOnUpdateNode(nodeRef); + invokeOnAddAspect(nodeRef, aspectTypeQName); + + // update the node status + nodeDaoService.recordChangeId(nodeRef); + } + } + + /** + * @see Node#getAspects() + */ + public void removeAspect(NodeRef nodeRef, QName aspectTypeQName) + throws InvalidNodeRefException, InvalidAspectException + { + // Invoke policy behaviours + invokeBeforeUpdateNode(nodeRef); + invokeBeforeRemoveAspect(nodeRef, aspectTypeQName); + + // get the aspect + AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName); + if (aspectDef == null) + { + throw new InvalidAspectException(aspectTypeQName); + } + // get the node + Node node = getNodeNotNull(nodeRef); + + // remove the aspect, if present + boolean removed = node.getAspects().remove(aspectTypeQName); + // if the aspect was present, remove the associated properties + if (removed) + { + Map nodeProperties = node.getProperties(); + Map propertyDefs = aspectDef.getProperties(); + for (QName propertyName : propertyDefs.keySet()) + { + nodeProperties.remove(propertyName); + } + + // Invoke policy behaviours + invokeOnUpdateNode(nodeRef); + invokeOnRemoveAspect(nodeRef, aspectTypeQName); + + // update the node status + nodeDaoService.recordChangeId(nodeRef); + } + } + + /** + * Performs a check on the set of node aspects + * + * @see Node#getAspects() + */ + public boolean hasAspect(NodeRef nodeRef, QName aspectRef) throws InvalidNodeRefException, InvalidAspectException + { + Node node = getNodeNotNull(nodeRef); + Set aspectQNames = node.getAspects(); + boolean hasAspect = aspectQNames.contains(aspectRef); + // done + return hasAspect; + } + + public Set getAspects(NodeRef nodeRef) throws InvalidNodeRefException + { + Node node = getNodeNotNull(nodeRef); + Set aspectQNames = node.getAspects(); + // copy the set to ensure initialization + Set ret = new HashSet(aspectQNames.size()); + ret.addAll(aspectQNames); + // done + return ret; + } + + public void deleteNode(NodeRef nodeRef) + { + boolean isArchivedNode = false; + boolean requiresDelete = false; + + // Invoke policy behaviours + invokeBeforeDeleteNode(nodeRef); + + // get the node + Node node = getNodeNotNull(nodeRef); + // get the primary parent-child relationship before it is gone + ChildAssociationRef childAssocRef = getPrimaryParent(nodeRef); + // get type and aspect QNames as they will be unavailable after the delete + QName nodeTypeQName = node.getTypeQName(); + Set nodeAspectQNames = node.getAspects(); + + // check if we need to archive the node + StoreRef archiveStoreRef = null; + if (nodeAspectQNames.contains(ContentModel.ASPECT_TEMPORARY)) + { + // the node has the temporary aspect meaning + // it can not be archived + requiresDelete = true; + isArchivedNode = false; + } + else + { + StoreRef storeRef = nodeRef.getStoreRef(); + archiveStoreRef = storeArchiveMap.getArchiveMap().get(storeRef); + // get the type and check if we need archiving + TypeDefinition typeDef = dictionaryService.getType(node.getTypeQName()); + if (typeDef == null || !typeDef.isArchive() || archiveStoreRef == null) + { + requiresDelete = true; + } + } + + if (requiresDelete) + { + // perform a normal deletion + nodeDaoService.deleteNode(node, true); + isArchivedNode = false; + } + else + { + // archive it + archiveNode(nodeRef, archiveStoreRef); + isArchivedNode = true; + } + + // Invoke policy behaviours + invokeOnDeleteNode(childAssocRef, nodeTypeQName, nodeAspectQNames, isArchivedNode); + } + + public ChildAssociationRef addChild(NodeRef parentRef, NodeRef childRef, QName assocTypeQName, QName assocQName) + { + // Invoke policy behaviours + invokeBeforeUpdateNode(parentRef); + invokeBeforeCreateChildAssociation(parentRef, childRef, assocTypeQName, assocQName); + + // get the parent node and ensure that it is a container node + Node parentNode = getNodeNotNull(parentRef); + // get the child node + Node childNode = getNodeNotNull(childRef); + // make the association + ChildAssoc assoc = nodeDaoService.newChildAssoc( + parentNode, + childNode, + false, + assocTypeQName, + assocQName); + // ensure name uniqueness + setChildUniqueName(childNode); + ChildAssociationRef assocRef = assoc.getChildAssocRef(); + NodeRef childNodeRef = assocRef.getChildRef(); + + // check that the child addition of the child has not created a cyclic relationship + // this functionality is provided for free in getPath + getPaths(childNodeRef, false); + + // Invoke policy behaviours + invokeOnCreateChildAssociation(assocRef); + invokeOnUpdateNode(parentRef); + + return assoc.getChildAssocRef(); + } + + public void removeChild(NodeRef parentRef, NodeRef childRef) throws InvalidNodeRefException + { + Node parentNode = getNodeNotNull(parentRef); + Node childNode = getNodeNotNull(childRef); + Long childNodeId = childNode.getId(); + + // get all the child assocs + ChildAssociationRef primaryAssocRef = null; + Collection assocs = nodeDaoService.getChildAssocs(parentNode); + assocs = new HashSet(assocs); // copy set as we will be modifying it + for (ChildAssoc assoc : assocs) + { + if (!assoc.getChild().getId().equals(childNodeId)) + { + continue; // not a matching association + } + ChildAssociationRef assocRef = assoc.getChildAssocRef(); + // Is this a primary association? + if (assoc.getIsPrimary()) + { + // keep the primary associaton for last + primaryAssocRef = assocRef; + } + else + { + // delete the association instance - it is not primary + invokeBeforeDeleteChildAssociation(assocRef); + nodeDaoService.deleteChildAssoc(assoc, true); // cascade + invokeOnDeleteChildAssociation(assocRef); + } + } + // remove the child if the primary association was a match + if (primaryAssocRef != null) + { + deleteNode(primaryAssocRef.getChildRef()); + } + + // Invoke policy behaviours + invokeOnUpdateNode(parentRef); + + // done + } + + public Map getProperties(NodeRef nodeRef) throws InvalidNodeRefException + { + Node node = getNodeNotNull(nodeRef); + return getPropertiesImpl(node); + } + + private Map getPropertiesImpl(Node node) throws InvalidNodeRefException + { + NodeRef nodeRef = node.getNodeRef(); + + Map nodeProperties = node.getProperties(); + Map ret = new HashMap(nodeProperties.size()); + // copy values + for (Map.Entry entry: nodeProperties.entrySet()) + { + QName propertyQName = entry.getKey(); + PropertyValue propertyValue = entry.getValue(); + // get the property definition + PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); + // convert to the correct type + Serializable value = makeSerializableValue(propertyDef, propertyValue); + // copy across + ret.put(propertyQName, value); + } + // spoof referencable properties + addReferencableProperties(nodeRef, node.getId(), ret); + // done + return ret; + } + + public Serializable getProperty(NodeRef nodeRef, QName qname) throws InvalidNodeRefException + { + // spoof referencable properties + if (qname.equals(ContentModel.PROP_STORE_PROTOCOL)) + { + return nodeRef.getStoreRef().getProtocol(); + } + else if (qname.equals(ContentModel.PROP_STORE_IDENTIFIER)) + { + return nodeRef.getStoreRef().getIdentifier(); + } + else if (qname.equals(ContentModel.PROP_NODE_UUID)) + { + return nodeRef.getId(); + } + + // get the property from the node + Node node = getNodeNotNull(nodeRef); + + if (qname.equals(ContentModel.PROP_NODE_DBID)) + { + return node.getId(); + } + + Map properties = node.getProperties(); + PropertyValue propertyValue = properties.get(qname); + + // get the property definition + PropertyDefinition propertyDef = dictionaryService.getProperty(qname); + // convert to the correct type + Serializable value = makeSerializableValue(propertyDef, propertyValue); + // done + return value; + } + + /** + * Ensures that all required properties are present on the node and copies the + * property values to the Node. + *

    + * To remove a property, remove it from the map before calling this method. + * Null-valued properties are allowed. + *

    + * If any of the values are null, a marker object is put in to mimic nulls. They will be turned back into + * a real nulls when the properties are requested again. + * + * @see Node#getProperties() + */ + public void setProperties(NodeRef nodeRef, Map properties) throws InvalidNodeRefException + { + Node node = getNodeNotNull(nodeRef); + + // Invoke policy behaviours + invokeBeforeUpdateNode(nodeRef); + + // Do the set properties + Map propertiesBefore = getPropertiesImpl(node); + Map propertiesAfter = setPropertiesImpl(node, properties); + + setChildUniqueName(node); // ensure uniqueness + + // Invoke policy behaviours + invokeOnUpdateNode(nodeRef); + invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter); + } + + /** + * Does the work of setting the property values. Returns a map containing the state of the properties after the set + * operation is complete. + * + * @param node the node + * @param properties the map of property values + * @return the map of property values after the set operation is complete + * @throws InvalidNodeRefException + */ + private Map setPropertiesImpl(Node node, Map properties) throws InvalidNodeRefException + { + ParameterCheck.mandatory("properties", properties); + + // remove referencable properties + removeReferencableProperties(properties); + + // copy properties onto node + Map nodeProperties = node.getProperties(); + nodeProperties.clear(); + + // check the property type and copy the values across + for (QName propertyQName : properties.keySet()) + { + PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); + Serializable value = properties.get(propertyQName); + // get a persistable value + PropertyValue propertyValue = makePropertyValue(propertyDef, value); + nodeProperties.put(propertyQName, propertyValue); + } + + // update the node status + NodeRef nodeRef = node.getNodeRef(); + nodeDaoService.recordChangeId(nodeRef); + + // Return the properties after + return Collections.unmodifiableMap(properties); + } + + /** + * Gets the properties map, sets the value (null is allowed) and checks that the new set + * of properties is valid. + * + * @see DbNodeServiceImpl.NullPropertyValue + */ + public void setProperty(NodeRef nodeRef, QName qname, Serializable value) throws InvalidNodeRefException + { + Assert.notNull(qname); + + // Invoke policy behaviours + invokeBeforeUpdateNode(nodeRef); + + // get the node + Node node = getNodeNotNull(nodeRef); + + // Do the set operation + Map propertiesBefore = getPropertiesImpl(node); + Map propertiesAfter = setPropertyImpl(node, qname, value); + + if (qname.equals(ContentModel.PROP_NAME)) + { + setChildUniqueName(node); // ensure uniqueness + } + + // Invoke policy behaviours + invokeOnUpdateNode(nodeRef); + invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter); + } + + /** + * Does the work of setting a property value. Returns the values of the properties after the set operation is + * complete. + * + * @param node the node + * @param qname the qname of the property + * @param value the value of the property + * @return the values of the properties after the set operation is complete + * @throws InvalidNodeRefException + */ + public Map setPropertyImpl(Node node, QName qname, Serializable value) throws InvalidNodeRefException + { + NodeRef nodeRef = node.getNodeRef(); + + Map properties = node.getProperties(); + PropertyDefinition propertyDef = dictionaryService.getProperty(qname); + // get a persistable value + PropertyValue propertyValue = makePropertyValue(propertyDef, value); + properties.put(qname, propertyValue); + + // update the node status + nodeDaoService.recordChangeId(nodeRef); + + return getPropertiesImpl(node); + } + + /** + * Transforms {@link Node#getParentAssocs()} to a new collection + */ + public Collection getParents(NodeRef nodeRef) throws InvalidNodeRefException + { + Node node = getNodeNotNull(nodeRef); + // get the assocs pointing to it + Collection parentAssocs = node.getParentAssocs(); + // list of results + Collection results = new ArrayList(parentAssocs.size()); + for (ChildAssoc assoc : parentAssocs) + { + // get the parent + Node parentNode = assoc.getParent(); + results.add(parentNode.getNodeRef()); + } + // done + return results; + } + + /** + * Filters out any associations if their qname is not a match to the given pattern. + */ + public List getParentAssocs(NodeRef nodeRef, QNamePattern typeQNamePattern, QNamePattern qnamePattern) + { + Node node = getNodeNotNull(nodeRef); + // get the assocs pointing to it + Collection parentAssocs = node.getParentAssocs(); + // shortcut if there are no assocs + if (parentAssocs.size() == 0) + { + return Collections.emptyList(); + } + // list of results + List results = new ArrayList(parentAssocs.size()); + for (ChildAssoc assoc : parentAssocs) + { + // does the qname match the pattern? + if (!qnamePattern.isMatch(assoc.getQname()) || !typeQNamePattern.isMatch(assoc.getTypeQName())) + { + // no match - ignore + continue; + } + results.add(assoc.getChildAssocRef()); + } + // done + return results; + } + + /** + * Filters out any associations if their qname is not a match to the given pattern. + */ + public List getChildAssocs(NodeRef nodeRef, QNamePattern typeQNamePattern, QNamePattern qnamePattern) + { + Node node = getNodeNotNull(nodeRef); + // get the assocs pointing from it + Collection childAssocRefs = nodeDaoService.getChildAssocRefs(node); + // shortcut if there are no assocs + if (childAssocRefs.size() == 0) + { + return Collections.emptyList(); + } + // sort results + ArrayList orderedList = new ArrayList(childAssocRefs); + Collections.sort(orderedList); + + // list of results + int nthSibling = 0; + Iterator iterator = orderedList.iterator(); + while(iterator.hasNext()) + { + ChildAssociationRef childAssocRef = iterator.next(); + // does the qname match the pattern? + if (!qnamePattern.isMatch(childAssocRef.getQName()) || !typeQNamePattern.isMatch(childAssocRef.getTypeQName())) + { + // no match - remove + iterator.remove(); + } + else + { + childAssocRef.setNthSibling(nthSibling); + nthSibling++; + } + } + // done + return orderedList; + } + + public NodeRef getChildByName(NodeRef nodeRef, QName assocTypeQName, String childName) + { + Node node = getNodeNotNull(nodeRef); + ChildAssoc childAssoc = nodeDaoService.getChildAssoc(node, assocTypeQName, childName); + if (childAssoc != null) + { + return childAssoc.getChild().getNodeRef(); + } + else + { + return null; + } + } + + public ChildAssociationRef getPrimaryParent(NodeRef nodeRef) throws InvalidNodeRefException + { + Node node = getNodeNotNull(nodeRef); + // get the primary parent assoc + ChildAssoc assoc = nodeDaoService.getPrimaryParentAssoc(node); + + // done - the assoc may be null for a root node + ChildAssociationRef assocRef = null; + if (assoc == null) + { + assocRef = new ChildAssociationRef(null, null, null, nodeRef); + } + else + { + assocRef = assoc.getChildAssocRef(); + } + return assocRef; + } + + public AssociationRef createAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName) + throws InvalidNodeRefException, AssociationExistsException + { + // Invoke policy behaviours + invokeBeforeUpdateNode(sourceRef); + + Node sourceNode = getNodeNotNull(sourceRef); + Node targetNode = getNodeNotNull(targetRef); + // see if it exists + NodeAssoc assoc = nodeDaoService.getNodeAssoc(sourceNode, targetNode, assocTypeQName); + if (assoc != null) + { + throw new AssociationExistsException(sourceRef, targetRef, assocTypeQName); + } + // we are sure that the association doesn't exist - make it + assoc = nodeDaoService.newNodeAssoc(sourceNode, targetNode, assocTypeQName); + AssociationRef assocRef = assoc.getNodeAssocRef(); + + // Invoke policy behaviours + invokeOnUpdateNode(sourceRef); + invokeOnCreateAssociation(assocRef); + + return assocRef; + } + + public void removeAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName) + throws InvalidNodeRefException + { + Node sourceNode = getNodeNotNull(sourceRef); + Node targetNode = getNodeNotNull(targetRef); + // get the association + NodeAssoc assoc = nodeDaoService.getNodeAssoc(sourceNode, targetNode, assocTypeQName); + if (assoc == null) + { + // nothing to remove + return; + } + AssociationRef assocRef = assoc.getNodeAssocRef(); + + // Invoke policy behaviours + invokeBeforeUpdateNode(sourceRef); + + // delete it + nodeDaoService.deleteNodeAssoc(assoc); + + // Invoke policy behaviours + invokeOnUpdateNode(sourceRef); + invokeOnDeleteAssociation(assocRef); + } + + public List getTargetAssocs(NodeRef sourceRef, QNamePattern qnamePattern) + { + Node sourceNode = getNodeNotNull(sourceRef); + // get all assocs to target + Collection assocs = nodeDaoService.getTargetNodeAssocs(sourceNode); + List nodeAssocRefs = new ArrayList(assocs.size()); + for (NodeAssoc assoc : assocs) + { + // check qname pattern + if (!qnamePattern.isMatch(assoc.getTypeQName())) + { + continue; // the assoc name doesn't match the pattern given + } + nodeAssocRefs.add(assoc.getNodeAssocRef()); + } + // done + return nodeAssocRefs; + } + + public List getSourceAssocs(NodeRef targetRef, QNamePattern qnamePattern) + { + Node targetNode = getNodeNotNull(targetRef); + // get all assocs to source + Collection assocs = nodeDaoService.getSourceNodeAssocs(targetNode); + List nodeAssocRefs = new ArrayList(assocs.size()); + for (NodeAssoc assoc : assocs) + { + // check qname pattern + if (!qnamePattern.isMatch(assoc.getTypeQName())) + { + continue; // the assoc name doesn't match the pattern given + } + nodeAssocRefs.add(assoc.getNodeAssocRef()); + } + // done + return nodeAssocRefs; + } + + /** + * Recursive method used to build up paths from a given node to the root. + *

    + * Whilst walking up the hierarchy to the root, some nodes may have a root aspect. + * Everytime one of these is encountered, a new path is farmed off, but the method + * continues to walk up the hierarchy. + * + * @param currentNode the node to start from, i.e. the child node to work upwards from + * @param currentPath the path from the current node to the descendent that we started from + * @param completedPaths paths that have reached the root are added to this collection + * @param assocStack the parent-child relationships traversed whilst building the path. + * Used to detected cyclic relationships. + * @param primaryOnly true if only the primary parent association must be traversed. + * If this is true, then the only root is the top level node having no parents. + * @throws CyclicChildRelationshipException + */ + private void prependPaths( + final Node currentNode, + final Path currentPath, + Collection completedPaths, + Stack assocStack, + boolean primaryOnly) + throws CyclicChildRelationshipException + { + NodeRef currentNodeRef = currentNode.getNodeRef(); + // get the parent associations of the given node + Collection parentAssocs = currentNode.getParentAssocs(); + // does the node have parents + boolean hasParents = parentAssocs.size() > 0; + // does the current node have a root aspect? + boolean isRoot = hasAspect(currentNodeRef, ContentModel.ASPECT_ROOT); + boolean isStoreRoot = currentNode.getTypeQName().equals(ContentModel.TYPE_STOREROOT); + + // look for a root. If we only want the primary root, then ignore all but the top-level root. + if (isRoot && !(primaryOnly && hasParents)) // exclude primary search with parents present + { + // create a one-sided assoc ref for the root node and prepend to the stack + // this effectively spoofs the fact that the current node is not below the root + // - we put this assoc in as the first assoc in the path must be a one-sided + // reference pointing to the root node + ChildAssociationRef assocRef = new ChildAssociationRef( + null, + null, + null, + getRootNode(currentNode.getNodeRef().getStoreRef())); + // create a path to save and add the 'root' assoc + Path pathToSave = new Path(); + Path.ChildAssocElement first = null; + for (Path.Element element: currentPath) + { + if (first == null) + { + first = (Path.ChildAssocElement) element; + } + else + { + pathToSave.append(element); + } + } + if (first != null) + { + // mimic an association that would appear if the current node was below + // the root node + // or if first beneath the root node it will make the real thing + ChildAssociationRef updateAssocRef = new ChildAssociationRef( + isStoreRoot ? ContentModel.ASSOC_CHILDREN : first.getRef().getTypeQName(), + getRootNode(currentNode.getNodeRef().getStoreRef()), + first.getRef().getQName(), + first.getRef().getChildRef()); + Path.Element newFirst = new Path.ChildAssocElement(updateAssocRef); + pathToSave.prepend(newFirst); + } + + Path.Element element = new Path.ChildAssocElement(assocRef); + pathToSave.prepend(element); + + // store the path just built + completedPaths.add(pathToSave); + } + + if (parentAssocs.size() == 0 && !isRoot) + { + throw new RuntimeException("Node without parents does not have root aspect: " + + currentNodeRef); + } + // walk up each parent association + for (ChildAssoc assoc : parentAssocs) + { + // does the association already exist in the stack + if (assocStack.contains(assoc)) + { + // the association was present already + throw new CyclicChildRelationshipException( + "Cyclic parent-child relationship detected: \n" + + " current node: " + currentNode + "\n" + + " current path: " + currentPath + "\n" + + " next assoc: " + assoc, + assoc); + } + // do we consider only primary assocs? + if (primaryOnly && !assoc.getIsPrimary()) + { + continue; + } + // build a path element + NodeRef parentRef = assoc.getParent().getNodeRef(); + QName qname = assoc.getQname(); + NodeRef childRef = assoc.getChild().getNodeRef(); + boolean isPrimary = assoc.getIsPrimary(); + // build a real association reference + ChildAssociationRef assocRef = new ChildAssociationRef(assoc.getTypeQName(), parentRef, qname, childRef, isPrimary, -1); + // Ordering is not important here: We are building distinct paths upwards + Path.Element element = new Path.ChildAssocElement(assocRef); + // create a new path that builds on the current path + Path path = new Path(); + path.append(currentPath); + // prepend element + path.prepend(element); + // get parent node + Node parentNode = assoc.getParent(); + + // push the assoc stack, recurse and pop + assocStack.push(assoc); + prependPaths(parentNode, path, completedPaths, assocStack, primaryOnly); + assocStack.pop(); + } + // done + } + + /** + * @see #getPaths(NodeRef, boolean) + * @see #prependPaths(Node, Path, Collection, Stack, boolean) + */ + public Path getPath(NodeRef nodeRef) throws InvalidNodeRefException + { + List paths = getPaths(nodeRef, true); // checks primary path count + if (paths.size() == 1) + { + return paths.get(0); // we know there is only one + } + throw new RuntimeException("Primary path count not checked"); // checked by getPaths() + } + + /** + * When searching for primaryOnly == true, checks that there is exactly + * one path. + * @see #prependPaths(Node, Path, Collection, Stack, boolean) + */ + public List getPaths(NodeRef nodeRef, boolean primaryOnly) throws InvalidNodeRefException + { + // get the starting node + Node node = getNodeNotNull(nodeRef); + // create storage for the paths - only need 1 bucket if we are looking for the primary path + List paths = new ArrayList(primaryOnly ? 1 : 10); + // create an empty current path to start from + Path currentPath = new Path(); + // create storage for touched associations + Stack assocStack = new Stack(); + // call recursive method to sort it out + prependPaths(node, currentPath, paths, assocStack, primaryOnly); + + // check that for the primary only case we have exactly one path + if (primaryOnly && paths.size() != 1) + { + throw new RuntimeException("Node has " + paths.size() + " primary paths: " + nodeRef); + } + + // done + if (loggerPaths.isDebugEnabled()) + { + StringBuilder sb = new StringBuilder(256); + if (primaryOnly) + { + sb.append("Primary paths"); + } + else + { + sb.append("Paths"); + } + sb.append(" for node ").append(nodeRef); + for (Path path : paths) + { + sb.append("\n").append(" ").append(path); + } + loggerPaths.debug(sb); + } + return paths; + } + + private void archiveNode(NodeRef nodeRef, StoreRef archiveStoreRef) + { + Node node = getNodeNotNull(nodeRef); + ChildAssoc primaryParentAssoc = nodeDaoService.getPrimaryParentAssoc(node); + + // add the aspect + Set aspects = node.getAspects(); + aspects.add(ContentModel.ASPECT_ARCHIVED); + Map properties = node.getProperties(); + PropertyValue archivedByProperty = makePropertyValue( + dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_BY), + AuthenticationUtil.getCurrentUserName()); + properties.put(ContentModel.PROP_ARCHIVED_BY, archivedByProperty); + PropertyValue archivedDateProperty = makePropertyValue( + dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_DATE), + new Date()); + properties.put(ContentModel.PROP_ARCHIVED_DATE, archivedDateProperty); + PropertyValue archivedPrimaryParentNodeRefProperty = makePropertyValue( + dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC), + primaryParentAssoc.getChildAssocRef()); + properties.put(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC, archivedPrimaryParentNodeRefProperty); + PropertyValue originalOwnerProperty = properties.get(ContentModel.PROP_OWNER); + PropertyValue originalCreatorProperty = properties.get(ContentModel.PROP_CREATOR); + if (originalOwnerProperty != null || originalCreatorProperty != null) + { + properties.put( + ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER, + originalOwnerProperty != null ? originalOwnerProperty : originalCreatorProperty); + } + + // change the node ownership + aspects.add(ContentModel.ASPECT_OWNABLE); + PropertyValue newOwnerProperty = makePropertyValue( + dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER), + AuthenticationUtil.getCurrentUserName()); + properties.put(ContentModel.PROP_OWNER, newOwnerProperty); + + // move the node + NodeRef archiveStoreRootNodeRef = getRootNode(archiveStoreRef); + moveNode( + nodeRef, + archiveStoreRootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedItem")); + + // get the IDs of all the node's primary children, including its own + Map nodesById = getNodeHierarchy(node, null); + + // Archive all the associations between the archived nodes and non-archived nodes + for (Node nodeToArchive : nodesById.values()) + { + archiveAssocs(nodeToArchive, nodesById); + } + + // the node reference has changed due to the store move + nodeRef = node.getNodeRef(); + } + + /** + * Performs all the necessary housekeeping involved in changing a node's store. + * This method cascades down through all the primary children of the node as + * well. + * + * @param node the node whose store is changing + * @param store the new store for the node + */ + private void moveNodeToStore(Node node, Store store) + { + // get the IDs of all the node's primary children, including its own + Map nodesById = getNodeHierarchy(node, null); + + // move each node into the archive store + for (Node nodeToMove : nodesById.values()) + { + NodeRef oldNodeRef = nodeToMove.getNodeRef(); + nodeToMove.setStore(store); + NodeRef newNodeRef = nodeToMove.getNodeRef(); + + // update old status + NodeStatus oldNodeStatus = nodeDaoService.getNodeStatus(oldNodeRef, true); + oldNodeStatus.setNode(null); + // create the new status + NodeStatus newNodeStatus = nodeDaoService.getNodeStatus(newNodeRef, true); + newNodeStatus.setNode(nodeToMove); + } + } + + /** + * Fill the map of all primary children below the given node. + * The given node will be added to the map and the method is recursive + * to all primary children. + * + * @param node the start of the hierarchy + * @param nodesById a map of nodes that will be reused as the return value + * @return Returns a map of nodes in the hierarchy keyed by their IDs + */ + private Map getNodeHierarchy(Node node, Map nodesById) + { + if (nodesById == null) + { + nodesById = new HashMap(23); + } + + Long id = node.getId(); + if (nodesById.containsKey(id)) + { + // this ID was already added - circular reference + logger.warn("Circular hierarchy found including node " + id); + return nodesById; + } + // add the node to the map + nodesById.put(id, node); + // recurse into the primary children + Collection childAssocs = nodeDaoService.getChildAssocs(node); + for (ChildAssoc childAssoc : childAssocs) + { + // cascade into primary associations + if (childAssoc.getIsPrimary()) + { + Node primaryChild = childAssoc.getChild(); + nodesById = getNodeHierarchy(primaryChild, nodesById); + } + } + return nodesById; + } + + /** + * Archive all associations to and from the given node, with the + * exception of associations to or from nodes in the given map. + *

    + * Primary parent associations are also ignored. + * + * @param node the node whose associations must be archived + * @param nodesById a map of nodes partaking in the archival process + */ + private void archiveAssocs(Node node, Map nodesById) + { + List childAssocsToDelete = new ArrayList(5); + // child associations + ArrayList archivedChildAssocRefs = new ArrayList(5); + Collection childAssocs = nodeDaoService.getChildAssocs(node); + for (ChildAssoc assoc : childAssocs) + { + Long relatedNodeId = assoc.getChild().getId(); + if (nodesById.containsKey(relatedNodeId)) + { + // a sibling in the archive process + continue; + } + childAssocsToDelete.add(assoc); + archivedChildAssocRefs.add(assoc.getChildAssocRef()); + } + // parent associations + ArrayList archivedParentAssocRefs = new ArrayList(5); + for (ChildAssoc assoc : node.getParentAssocs()) + { + Long relatedNodeId = assoc.getParent().getId(); + if (nodesById.containsKey(relatedNodeId)) + { + // a sibling in the archive process + continue; + } + else if (assoc.getIsPrimary()) + { + // ignore the primary parent as this is handled more specifically + continue; + } + childAssocsToDelete.add(assoc); + archivedParentAssocRefs.add(assoc.getChildAssocRef()); + } + + List nodeAssocsToDelete = new ArrayList(5); + // source associations + ArrayList archivedSourceAssocRefs = new ArrayList(5); + for (NodeAssoc assoc : nodeDaoService.getSourceNodeAssocs(node)) + { + Long relatedNodeId = assoc.getSource().getId(); + if (nodesById.containsKey(relatedNodeId)) + { + // a sibling in the archive process + continue; + } + nodeAssocsToDelete.add(assoc); + archivedSourceAssocRefs.add(assoc.getNodeAssocRef()); + } + // target associations + ArrayList archivedTargetAssocRefs = new ArrayList(5); + for (NodeAssoc assoc : nodeDaoService.getTargetNodeAssocs(node)) + { + Long relatedNodeId = assoc.getTarget().getId(); + if (nodesById.containsKey(relatedNodeId)) + { + // a sibling in the archive process + continue; + } + nodeAssocsToDelete.add(assoc); + archivedTargetAssocRefs.add(assoc.getNodeAssocRef()); + } + // delete child assocs + for (ChildAssoc assoc : childAssocsToDelete) + { + nodeDaoService.deleteChildAssoc(assoc, false); + } + // delete node assocs + for (NodeAssoc assoc : nodeAssocsToDelete) + { + nodeDaoService.deleteNodeAssoc(assoc); + } + + // add archived aspect + node.getAspects().add(ContentModel.ASPECT_ARCHIVED_ASSOCS); + // set properties + Map properties = node.getProperties(); + + if (archivedParentAssocRefs.size() > 0) + { + PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_PARENT_ASSOCS); + PropertyValue propertyValue = makePropertyValue(propertyDef, archivedParentAssocRefs); + properties.put(ContentModel.PROP_ARCHIVED_PARENT_ASSOCS, propertyValue); + } + if (archivedChildAssocRefs.size() > 0) + { + PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_CHILD_ASSOCS); + PropertyValue propertyValue = makePropertyValue(propertyDef, archivedChildAssocRefs); + properties.put(ContentModel.PROP_ARCHIVED_CHILD_ASSOCS, propertyValue); + } + if (archivedSourceAssocRefs.size() > 0) + { + PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS); + PropertyValue propertyValue = makePropertyValue(propertyDef, archivedSourceAssocRefs); + properties.put(ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS, propertyValue); + } + if (archivedTargetAssocRefs.size() > 0) + { + PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_TARGET_ASSOCS); + PropertyValue propertyValue = makePropertyValue(propertyDef, archivedTargetAssocRefs); + properties.put(ContentModel.PROP_ARCHIVED_TARGET_ASSOCS, propertyValue); + } + } + + public NodeRef getStoreArchiveNode(StoreRef storeRef) + { + StoreRef archiveStoreRef = storeArchiveMap.getArchiveMap().get(storeRef); + if (archiveStoreRef == null) + { + // no mapping for the given store + return null; + } + else + { + return getRootNode(archiveStoreRef); + } + } + + public NodeRef restoreNode(NodeRef archivedNodeRef, NodeRef destinationParentNodeRef, QName assocTypeQName, QName assocQName) + { + Node archivedNode = getNodeNotNull(archivedNodeRef); + Set aspects = archivedNode.getAspects(); + Map properties = archivedNode.getProperties(); + // the node must be a top-level archive node + if (!aspects.contains(ContentModel.ASPECT_ARCHIVED)) + { + throw new AlfrescoRuntimeException("The node to archive is not an archive node"); + } + ChildAssociationRef originalPrimaryParentAssocRef = (ChildAssociationRef) makeSerializableValue( + dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC), + properties.get(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC)); + PropertyValue originalOwnerProperty = properties.get(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER); + // remove the aspect archived aspect + aspects.remove(ContentModel.ASPECT_ARCHIVED); + properties.remove(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC); + properties.remove(ContentModel.PROP_ARCHIVED_BY); + properties.remove(ContentModel.PROP_ARCHIVED_DATE); + properties.remove(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER); + + // restore the original ownership + if (originalOwnerProperty != null) + { + aspects.add(ContentModel.ASPECT_OWNABLE); + properties.put(ContentModel.PROP_OWNER, originalOwnerProperty); + } + + if (destinationParentNodeRef == null) + { + // we must restore to the original location + destinationParentNodeRef = originalPrimaryParentAssocRef.getParentRef(); + } + // check the associations + if (assocTypeQName == null) + { + assocTypeQName = originalPrimaryParentAssocRef.getTypeQName(); + } + if (assocQName == null) + { + assocQName = originalPrimaryParentAssocRef.getQName(); + } + + // move the node to the target parent, which may or may not be the original parent + moveNode( + archivedNodeRef, + destinationParentNodeRef, + assocTypeQName, + assocQName); + + // get the IDs of all the node's primary children, including its own + Map restoredNodesById = getNodeHierarchy(archivedNode, null); + // Restore the archived associations, if required + for (Node restoredNode : restoredNodesById.values()) + { + restoreAssocs(restoredNode); + } + + // the node reference has changed due to the store move + NodeRef restoredNodeRef = archivedNode.getNodeRef(); + + // done + if (logger.isDebugEnabled()) + { + logger.debug("Restored node: \n" + + " original noderef: " + archivedNodeRef + "\n" + + " restored noderef: " + restoredNodeRef + "\n" + + " new parent: " + destinationParentNodeRef); + } + return restoredNodeRef; + } + + private void restoreAssocs(Node node) + { + NodeRef nodeRef = node.getNodeRef(); + // set properties + Map properties = node.getProperties(); + + // restore parent associations + Collection parentAssocRefs = (Collection) getProperty( + nodeRef, + ContentModel.PROP_ARCHIVED_PARENT_ASSOCS); + if (parentAssocRefs != null) + { + for (ChildAssociationRef assocRef : parentAssocRefs) + { + NodeRef parentNodeRef = assocRef.getParentRef(); + if (!exists(parentNodeRef)) + { + continue; + } + Node parentNode = getNodeNotNull(parentNodeRef); + // get the name to use for the unique child check + QName assocTypeQName = assocRef.getTypeQName(); + nodeDaoService.newChildAssoc( + parentNode, + node, + assocRef.isPrimary(), + assocTypeQName, + assocRef.getQName()); + } + properties.remove(ContentModel.PROP_ARCHIVED_PARENT_ASSOCS); + } + + // make sure that the node name uniqueness is enforced + setChildUniqueName(node); + + // restore child associations + Collection childAssocRefs = (Collection) getProperty( + nodeRef, + ContentModel.PROP_ARCHIVED_CHILD_ASSOCS); + if (childAssocRefs != null) + { + for (ChildAssociationRef assocRef : childAssocRefs) + { + NodeRef childNodeRef = assocRef.getChildRef(); + if (!exists(childNodeRef)) + { + continue; + } + Node childNode = getNodeNotNull(childNodeRef); + QName assocTypeQName = assocRef.getTypeQName(); + // get the name to use for the unique child check + nodeDaoService.newChildAssoc( + node, + childNode, + assocRef.isPrimary(), + assocTypeQName, + assocRef.getQName()); + // ensure that the name uniqueness is enforced for the child node + setChildUniqueName(childNode); + } + properties.remove(ContentModel.PROP_ARCHIVED_CHILD_ASSOCS); + } + // restore source associations + Collection sourceAssocRefs = (Collection) getProperty( + nodeRef, + ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS); + if (sourceAssocRefs != null) + { + for (AssociationRef assocRef : sourceAssocRefs) + { + NodeRef sourceNodeRef = assocRef.getSourceRef(); + if (!exists(sourceNodeRef)) + { + continue; + } + Node sourceNode = getNodeNotNull(sourceNodeRef); + nodeDaoService.newNodeAssoc(sourceNode, node, assocRef.getTypeQName()); + } + properties.remove(ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS); + } + // restore target associations + Collection targetAssocRefs = (Collection) getProperty( + nodeRef, + ContentModel.PROP_ARCHIVED_TARGET_ASSOCS); + if (targetAssocRefs != null) + { + for (AssociationRef assocRef : targetAssocRefs) + { + NodeRef targetNodeRef = assocRef.getTargetRef(); + if (!exists(targetNodeRef)) + { + continue; + } + Node targetNode = getNodeNotNull(targetNodeRef); + nodeDaoService.newNodeAssoc(node, targetNode, assocRef.getTypeQName()); + } + properties.remove(ContentModel.PROP_ARCHIVED_TARGET_ASSOCS); + } + // remove the aspect + node.getAspects().remove(ContentModel.ASPECT_ARCHIVED_ASSOCS); + } + + /** + * Checks the dictionary's definition of the association to assign a unique name to the child node. + * + * @param assocTypeQName the type of the child association + * @param childNode the child node being added. The name will be extracted from it, if necessary. + * @return Returns the value to be put on the child association for uniqueness, or null if + */ + private void setChildUniqueName(Node childNode) + { + // get the name property + Map properties = childNode.getProperties(); + PropertyValue nameValue = properties.get(ContentModel.PROP_NAME); + String useName = null; + if (nameValue == null) + { + // no name has been assigned, so assign the ID of the child node + useName = childNode.getUuid(); + } + else + { + useName = (String) nameValue.getValue(DataTypeDefinition.TEXT); + } + // get all the parent assocs + Collection parentAssocs = childNode.getParentAssocs(); + for (ChildAssoc assoc : parentAssocs) + { + QName assocTypeQName = assoc.getTypeQName(); + AssociationDefinition assocDef = dictionaryService.getAssociation(assocTypeQName); + if (!assocDef.isChild()) + { + throw new DataIntegrityViolationException("Child association has non-child type: " + assoc.getId()); + } + ChildAssociationDefinition childAssocDef = (ChildAssociationDefinition) assocDef; + if (childAssocDef.getDuplicateChildNamesAllowed()) + { + // the name is irrelevant, so it doesn't need to be put into the unique key + nodeDaoService.setChildNameUnique(assoc, null); + } + else + { + nodeDaoService.setChildNameUnique(assoc, useName); + } + } + // done + if (logger.isDebugEnabled()) + { + logger.debug( + "Unique name set for all " + parentAssocs.size() + " parent associations: \n" + + " name: " + useName); + } + } +} diff --git a/source/java/org/alfresco/repo/node/db/NodeDaoService.java b/source/java/org/alfresco/repo/node/db/NodeDaoService.java index c44fb7fca7..2e102f7aa0 100644 --- a/source/java/org/alfresco/repo/node/db/NodeDaoService.java +++ b/source/java/org/alfresco/repo/node/db/NodeDaoService.java @@ -236,11 +236,14 @@ public interface NodeDaoService */ public List getPropertyValuesByActualType(DataTypeDefinition actualDataTypeDefinition); - public Transaction getLastTxn(final StoreRef storeRef); - public int getTxnUpdateCountForStore(final StoreRef storeRef, final long txnId); - public int getTxnDeleteCountForStore(final StoreRef storeRef, final long txnId); + public Transaction getTxnById(long txnId); + public Transaction getLastTxn(); + public Transaction getLastTxnForStore(final StoreRef storeRef); + public int getTxnUpdateCount(final long txnId); + public int getTxnDeleteCount(final long txnId); public int getTransactionCount(); - public List getNextTxns(final Transaction lastTxn, final int count); + public List getNextTxns(final long lastTxnId, final int count); + public List getNextRemoteTxns(final long lastTxnId, final int count); public List getTxnChangesForStore(final StoreRef storeRef, final long txnId); public List getTxnChanges(final long txnId); } diff --git a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java index 8881eea7d6..cc702dc894 100644 --- a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java @@ -1,1165 +1,1216 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.repo.node.db.hibernate; - -import java.io.Serializable; -import java.net.InetAddress; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.zip.CRC32; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.domain.ChildAssoc; -import org.alfresco.repo.domain.Node; -import org.alfresco.repo.domain.NodeAssoc; -import org.alfresco.repo.domain.NodeKey; -import org.alfresco.repo.domain.NodeStatus; -import org.alfresco.repo.domain.PropertyValue; -import org.alfresco.repo.domain.Server; -import org.alfresco.repo.domain.Store; -import org.alfresco.repo.domain.StoreKey; -import org.alfresco.repo.domain.Transaction; -import org.alfresco.repo.domain.hibernate.ChildAssocImpl; -import org.alfresco.repo.domain.hibernate.NodeAssocImpl; -import org.alfresco.repo.domain.hibernate.NodeImpl; -import org.alfresco.repo.domain.hibernate.NodeStatusImpl; -import org.alfresco.repo.domain.hibernate.ServerImpl; -import org.alfresco.repo.domain.hibernate.StoreImpl; -import org.alfresco.repo.domain.hibernate.TransactionImpl; -import org.alfresco.repo.node.db.NodeDaoService; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport; -import org.alfresco.repo.transaction.TransactionAwareSingleton; -import org.alfresco.repo.transaction.TransactionalDao; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.InvalidTypeException; -import org.alfresco.service.cmr.repository.AssociationExistsException; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; -import org.alfresco.service.cmr.repository.datatype.TypeConverter; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.GUID; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.hibernate.ObjectDeletedException; -import org.hibernate.Query; -import org.hibernate.ScrollMode; -import org.hibernate.ScrollableResults; -import org.hibernate.Session; -import org.springframework.dao.DataAccessException; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.orm.hibernate3.HibernateCallback; -import org.springframework.orm.hibernate3.support.HibernateDaoSupport; - -/** - * Hibernate-specific implementation of the persistence-independent node DAO interface - * - * @author Derek Hulley - */ -public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements NodeDaoService, TransactionalDao -{ - private static final String QUERY_GET_ALL_STORES = "store.GetAllStores"; - private static final String UPDATE_SET_CHILD_ASSOC_NAME = "node.updateChildAssocName"; - private static final String QUERY_GET_CHILD_ASSOCS = "node.GetChildAssocs"; - private static final String QUERY_GET_CHILD_ASSOC_BY_TYPE_AND_NAME = "node.GetChildAssocByTypeAndName"; - private static final String QUERY_GET_CHILD_ASSOC_REFS = "node.GetChildAssocRefs"; - private static final String QUERY_GET_NODE_ASSOC = "node.GetNodeAssoc"; - private static final String QUERY_GET_NODE_ASSOCS_TO_AND_FROM = "node.GetNodeAssocsToAndFrom"; - private static final String QUERY_GET_TARGET_ASSOCS = "node.GetTargetAssocs"; - private static final String QUERY_GET_SOURCE_ASSOCS = "node.GetSourceAssocs"; - private static final String QUERY_GET_NODES_WITH_PROPERTY_VALUES_BY_ACTUAL_TYPE = "node.GetNodesWithPropertyValuesByActualType"; - private static final String QUERY_GET_SERVER_BY_IPADDRESS = "server.getServerByIpAddress"; - - private static Log logger = LogFactory.getLog(HibernateNodeDaoServiceImpl.class); - - /** a uuid identifying this unique instance */ - private final String uuid; - - private static TransactionAwareSingleton serverIdSingleton = new TransactionAwareSingleton(); - - /** - * - */ - public HibernateNodeDaoServiceImpl() - { - this.uuid = GUID.generate(); - } - - /** - * Checks equality by type and uuid - */ - public boolean equals(Object obj) - { - if (obj == null) - { - return false; - } - else if (!(obj instanceof HibernateNodeDaoServiceImpl)) - { - return false; - } - HibernateNodeDaoServiceImpl that = (HibernateNodeDaoServiceImpl) obj; - return this.uuid.equals(that.uuid); - } - - /** - * @see #uuid - */ - public int hashCode() - { - return uuid.hashCode(); - } - - /** - * Gets/creates the server instance to use for the life of this instance - */ - private Server getServer() - { - Long serverId = serverIdSingleton.get(); - Server server = null; - if (serverId != null) - { - server = (Server) getSession().get(ServerImpl.class, serverId); - if (server != null) - { - return server; - } - } - try - { - final String ipAddress = InetAddress.getLocalHost().getHostAddress(); - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session - .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_SERVER_BY_IPADDRESS) - .setString("ipAddress", ipAddress); - return query.uniqueResult(); - } - }; - server = (Server) getHibernateTemplate().execute(callback); - // create it if it doesn't exist - if (server == null) - { - server = new ServerImpl(); - server.setIpAddress(ipAddress); - try - { - getSession().save(server); - } - catch (DataIntegrityViolationException e) - { - // get it again - server = (Server) getHibernateTemplate().execute(callback); - if (server == null) - { - throw new AlfrescoRuntimeException("Unable to create server instance: " + ipAddress); - } - } - } - // push the value into the singleton - serverIdSingleton.put(server.getId()); - - return server; - } - catch (Exception e) - { - throw new AlfrescoRuntimeException("Failed to create server instance", e); - } - } - - private static final String RESOURCE_KEY_TRANSACTION_ID = "hibernate.transaction.id"; - private Transaction getCurrentTransaction() - { - Transaction transaction = null; - Serializable txnId = (Serializable) AlfrescoTransactionSupport.getResource(RESOURCE_KEY_TRANSACTION_ID); - if (txnId == null) - { - // no transaction instance has been bound to the transaction - transaction = new TransactionImpl(); - transaction.setChangeTxnId(AlfrescoTransactionSupport.getTransactionId()); - transaction.setServer(getServer()); - txnId = getHibernateTemplate().save(transaction); - // bind the id - AlfrescoTransactionSupport.bindResource(RESOURCE_KEY_TRANSACTION_ID, txnId); - } - else - { - transaction = (Transaction) getHibernateTemplate().get(TransactionImpl.class, txnId); - } - return transaction; - } - - /** - * Does this Session contain any changes which must be - * synchronized with the store? - * - * @return true => changes are pending - */ - public boolean isDirty() - { - // create a callback for the task - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - return session.isDirty(); - } - }; - // execute the callback - return ((Boolean)getHibernateTemplate().execute(callback)).booleanValue(); - } - - /** - * Just flushes the session - */ - public void flush() - { - getSession().flush(); - } - - /** - * @see #QUERY_GET_ALL_STORES - */ - @SuppressWarnings("unchecked") - public List getStores() - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session.getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_ALL_STORES); - return query.list(); - } - }; - List queryResults = (List) getHibernateTemplate().execute(callback); - // done - return queryResults; - } - - /** - * Ensures that the store protocol/identifier combination is unique - */ - public Store createStore(String protocol, String identifier) - { - // ensure that the name isn't in use - Store store = getStore(protocol, identifier); - if (store != null) - { - throw new RuntimeException("A store already exists: \n" + - " protocol: " + protocol + "\n" + - " identifier: " + identifier + "\n" + - " store: " + store); - } - - store = new StoreImpl(); - // set key - store.setKey(new StoreKey(protocol, identifier)); - // persist so that it is present in the hibernate cache - getHibernateTemplate().save(store); - // create and assign a root node - Node rootNode = newNode( - store, - GUID.generate(), - ContentModel.TYPE_STOREROOT); - store.setRootNode(rootNode); - // done - return store; - } - - public Store getStore(String protocol, String identifier) - { - StoreKey storeKey = new StoreKey(protocol, identifier); - Store store = (Store) getHibernateTemplate().get(StoreImpl.class, storeKey); - // done - return store; - } - - /** - * Fetch the node status, if it exists - */ - public NodeStatus getNodeStatus(NodeRef nodeRef, boolean update) - { - NodeKey nodeKey = new NodeKey(nodeRef); - NodeStatus status = null; - try - { - status = (NodeStatus) getHibernateTemplate().get(NodeStatusImpl.class, nodeKey); - } - catch (DataAccessException e) - { - if (e.contains(ObjectDeletedException.class)) - { - // the object no longer exists - return null; - } - throw e; - } - // create if necessary - if (status == null && update) - { - status = new NodeStatusImpl(); - status.setKey(nodeKey); - status.setTransaction(getCurrentTransaction()); - getHibernateTemplate().save(status); - } - else if (status != null && update) - { - // update the transaction - status.setTransaction(getCurrentTransaction()); - } - // done - return status; - } - - public void recordChangeId(NodeRef nodeRef) - { - NodeKey key = new NodeKey(nodeRef); - - NodeStatus status = (NodeStatus) getHibernateTemplate().get(NodeStatusImpl.class, key); - if (status == null) - { - // the node never existed or the status was deleted - return; - } - else - { - status.getTransaction().setChangeTxnId(AlfrescoTransactionSupport.getTransactionId()); - } - } - - public Node newNode(Store store, String uuid, QName nodeTypeQName) throws InvalidTypeException - { - NodeKey key = new NodeKey(store.getKey(), uuid); - - // create (or reuse) the mandatory node status - NodeStatus status = (NodeStatus) getHibernateTemplate().get(NodeStatusImpl.class, key); - if (status == null) - { - status = new NodeStatusImpl(); - status.setKey(key); - } - else - { - // The node existed at some point. - // Although unlikely, it is possible that the node was deleted in this transaction. - // If that is the case, then the session has to be flushed so that the database - // constraints aren't violated as the node creation will write to the database to - // get an ID - if (status.getTransaction().getChangeTxnId().equals(AlfrescoTransactionSupport.getTransactionId())) - { - // flush - getHibernateTemplate().flush(); - } - } - - // build a concrete node based on a bootstrap type - Node node = new NodeImpl(); - // set other required properties - node.setStore(store); - node.setUuid(uuid); - node.setTypeQName(nodeTypeQName); - // persist the node - getHibernateTemplate().save(node); - - // set required status properties - status.setNode(node); - // assign a transaction - if (status.getTransaction() == null) - { - status.setTransaction(getCurrentTransaction()); - } - // persist the nodestatus - getHibernateTemplate().save(status); - - // done - return node; - } - - public Node getNode(NodeRef nodeRef) - { - // get it via the node status - NodeStatus status = getNodeStatus(nodeRef, false); - if (status == null) - { - // no status implies no node - return null; - } - else - { - // a status may have a node - Node node = status.getNode(); - return node; - } - } - - /** - * Manually ensures that all cascading of associations is taken care of - */ - public void deleteNode(Node node, boolean cascade) - { - Set deletedChildAssocIds = new HashSet(10); - deleteNodeInternal(node, cascade, deletedChildAssocIds); - } - - /** - * - * @param node - * @param cascade true to cascade delete - * @param deletedChildAssocIds previously deleted child associations - */ - private void deleteNodeInternal(Node node, boolean cascade, Set deletedChildAssocIds) - { - // delete all parent assocs - if (logger.isDebugEnabled()) - { - logger.debug("Deleting parent assocs of node " + node.getId()); - } - - Collection parentAssocs = node.getParentAssocs(); - parentAssocs = new ArrayList(parentAssocs); - for (ChildAssoc assoc : parentAssocs) - { - deleteChildAssocInternal(assoc, false, deletedChildAssocIds); // we don't cascade upwards - } - // delete all child assocs - if (logger.isDebugEnabled()) - { - logger.debug("Deleting child assocs of node " + node.getId()); - } - Collection childAssocs = getChildAssocs(node); - childAssocs = new ArrayList(childAssocs); - for (ChildAssoc assoc : childAssocs) - { - deleteChildAssocInternal(assoc, cascade, deletedChildAssocIds); // potentially cascade downwards - } - // delete all node associations to and from - if (logger.isDebugEnabled()) - { - logger.debug("Deleting source and target assocs of node " + node.getId()); - } - List nodeAssocs = getNodeAssocsToAndFrom(node); - for (NodeAssoc assoc : nodeAssocs) - { - getHibernateTemplate().delete(assoc); - } - // update the node status - NodeRef nodeRef = node.getNodeRef(); - NodeStatus nodeStatus = getNodeStatus(nodeRef, true); - nodeStatus.setNode(null); - nodeStatus.getTransaction().setChangeTxnId(AlfrescoTransactionSupport.getTransactionId()); - // finally delete the node - getHibernateTemplate().delete(node); - // flush to ensure constraints can't be violated - getSession().flush(); - // done - } - - private long getCrc(String str) - { - CRC32 crc = new CRC32(); - crc.update(str.getBytes()); - return crc.getValue(); - } - - private static final String TRUNCATED_NAME_INDICATOR = "~~~"; - private String getShortName(String str) - { - int length = str.length(); - if (length <= 50) - { - return str; - } - else - { - StringBuilder ret = new StringBuilder(50); - ret.append(str.substring(0, 47)).append(TRUNCATED_NAME_INDICATOR); - return ret.toString(); - } - } - - public ChildAssoc newChildAssoc( - Node parentNode, - Node childNode, - boolean isPrimary, - QName assocTypeQName, - QName qname) - { - /* - * This initial child association creation will fail IFF there is already - * an association of the given type and name between the two nodes. For new association - * creation, this can only occur if two transactions attempt to create a secondary - * child association between the same two nodes. As this is unlikely, it is - * appropriate to just throw a runtime exception and let the second transaction - * fail. - * - * We don't need to flush the session beforehand as there won't be any deletions - * of the assocation pending. The association deletes, on the other hand, have - * to flush early in order to ensure that the database unique index isn't violated - * if the association is recreated subsequently. - */ - - // assign a random name to the node - String randomName = GUID.generate(); - - ChildAssoc assoc = new ChildAssocImpl(); - assoc.setTypeQName(assocTypeQName); - assoc.setChildNodeName(randomName); - assoc.setChildNodeNameCrc(-1L); // random names compete only with each other - assoc.setQname(qname); - assoc.setIsPrimary(isPrimary); - // maintain inverse sets - assoc.buildAssociation(parentNode, childNode); - // persist it - getHibernateTemplate().save(assoc); - // done - return assoc; - } - - public void setChildNameUnique(final ChildAssoc childAssoc, String childName) - { - /* - * As the Hibernate session is rendered useless when an exception is - * bubbled up, we go direct to the database to update the child association. - * This preserves the session and client code can catch the resulting - * exception and react to it whilst in the same transaction. - * - * We ensure that case-insensitivity is maintained by persisting - * the lowercase version of the child node name. - */ - - String childNameNew = null; - long crc = -1; - if (childName == null) - { - // random names compete only with each other, i.e. not at all - childNameNew = GUID.generate(); - crc = -1; - } - else - { - // assigned names compete exactly - childNameNew = childName.toLowerCase(); - crc = getCrc(childNameNew); - } - - final String childNameNewShort = getShortName(childNameNew); - final long childNameNewCrc = crc; - - // check if the name has changed - if (childAssoc.getChildNodeNameCrc() == childNameNewCrc) - { - if (childAssoc.getChildNodeName().equals(childNameNewShort)) - { - // nothing changed - return; - } - } - - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - session.flush(); - Query query = session - .getNamedQuery(HibernateNodeDaoServiceImpl.UPDATE_SET_CHILD_ASSOC_NAME) - .setString("newName", childNameNewShort) - .setLong("newNameCrc", childNameNewCrc) - .setLong("childAssocId", childAssoc.getId()); - return (Integer) query.executeUpdate(); - } - }; - try - { - Integer count = (Integer) getHibernateTemplate().execute(callback); - // refresh the entity directly - if (count.intValue() == 0) - { - if (logger.isDebugEnabled()) - { - logger.debug("ChildAssoc not updated: " + childAssoc.getId()); - } - } - else - { - getHibernateTemplate().refresh(childAssoc); - } - } - catch (DataIntegrityViolationException e) - { - NodeRef parentNodeRef = childAssoc.getParent().getNodeRef(); - QName assocTypeQName = childAssoc.getTypeQName(); - throw new DuplicateChildNodeNameException(parentNodeRef, assocTypeQName, childName); - } - } - - @SuppressWarnings("unchecked") - public Collection getChildAssocs(final Node parentNode) - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session - .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOCS) - .setLong("parentId", parentNode.getId()); - return query.list(); - } - }; - List queryResults = (List) getHibernateTemplate().execute(callback); - return queryResults; - } - - @SuppressWarnings("unchecked") - public Collection getChildAssocRefs(final Node parentNode) - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session - .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOC_REFS) - .setLong("parentId", parentNode.getId()); - return query.list(); - } - }; - List queryResults = (List) getHibernateTemplate().execute(callback); - Collection refs = new ArrayList(queryResults.size()); - NodeRef parentNodeRef = parentNode.getNodeRef(); - for (Object[] row : queryResults) - { - String childProtocol = (String) row[5]; - String childIdentifier = (String) row[6]; - String childUuid = (String) row[7]; - NodeRef childNodeRef = new NodeRef(new StoreRef(childProtocol, childIdentifier), childUuid); - QName assocTypeQName = (QName) row[0]; - QName assocQName = (QName) row[1]; - Boolean assocIsPrimary = (Boolean) row[2]; - Integer assocIndex = (Integer) row[3]; - ChildAssociationRef assocRef = new ChildAssociationRef( - assocTypeQName, - parentNodeRef, - assocQName, - childNodeRef, - assocIsPrimary.booleanValue(), - assocIndex.intValue()); - refs.add(assocRef); - } - return refs; - } - - public ChildAssoc getChildAssoc( - Node parentNode, - Node childNode, - QName assocTypeQName, - QName qname) - { - ChildAssociationRef childAssocRef = new ChildAssociationRef( - assocTypeQName, - parentNode.getNodeRef(), - qname, - childNode.getNodeRef()); - // get all the parent's child associations - Collection assocs = getChildAssocs(parentNode); - // hunt down the desired assoc - for (ChildAssoc assoc : assocs) - { - // is it a match? - if (!assoc.getChildAssocRef().equals(childAssocRef)) // not a match - { - continue; - } - else - { - return assoc; - } - } - // not found - return null; - } - - public ChildAssoc getChildAssoc(final Node parentNode, final QName assocTypeQName, final String childName) - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - String childNameLower = childName.toLowerCase(); - String childNameShort = getShortName(childNameLower); - long childNameLowerCrc = getCrc(childNameLower); - Query query = session - .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOC_BY_TYPE_AND_NAME) - .setLong("parentId", parentNode.getId()) - .setParameter("typeQName", assocTypeQName) - .setParameter("childNodeName", childNameShort) - .setLong("childNodeNameCrc", childNameLowerCrc); - return query.uniqueResult(); - } - }; - ChildAssoc childAssoc = (ChildAssoc) getHibernateTemplate().execute(callback); - return childAssoc; - } - - /** - * Public level entry-point. - */ - public void deleteChildAssoc(ChildAssoc assoc, boolean cascade) - { - Set deletedChildAssocIds = new HashSet(10); - deleteChildAssocInternal(assoc, cascade, deletedChildAssocIds); - } - - /** - * Cascade deletion of child associations, recording the IDs of deleted assocs. - * - * @param assoc the association to delete - * @param cascade true to cascade to the child node of the association - * @param deletedChildAssocIds already-deleted associations - */ - private void deleteChildAssocInternal(ChildAssoc assoc, boolean cascade, Set deletedChildAssocIds) - { - Long childAssocId = assoc.getId(); - if (deletedChildAssocIds.contains(childAssocId)) - { - if (logger.isDebugEnabled()) - { - logger.debug("Ignoring parent-child association " + assoc.getId()); - } - return; - } - - if (logger.isDebugEnabled()) - { - logger.debug( - "Deleting parent-child association " + assoc.getId() + - (cascade ? " with" : " without") + " cascade:" + - assoc.getParent().getId() + " -> " + assoc.getChild().getId()); - } - - Node childNode = assoc.getChild(); - - // maintain inverse association sets - assoc.removeAssociation(); - // remove instance - getHibernateTemplate().delete(assoc); - deletedChildAssocIds.add(childAssocId); // ensure that we don't attempt to delete it twice - - if (cascade && assoc.getIsPrimary()) // the assoc is primary - { - // delete the child node - deleteNodeInternal(childNode, cascade, deletedChildAssocIds); - /* - * The child node deletion will cascade delete all assocs to - * and from it, but we have safely removed this one, so no - * duplicate call will be received to do this - */ - } - - // To ensure the validity of the constraint enforcement by the database, - // we have to flush here - getSession().flush(); - } - - public ChildAssoc getPrimaryParentAssoc(Node node) - { - // get the assocs pointing to the node - Collection parentAssocs = node.getParentAssocs(); - ChildAssoc primaryAssoc = null; - for (ChildAssoc assoc : parentAssocs) - { - // ignore non-primary assocs - if (!assoc.getIsPrimary()) - { - continue; - } - else if (primaryAssoc != null) - { - // we have more than one somehow - throw new DataIntegrityViolationException( - "Multiple primary associations: \n" + - " child: " + node + "\n" + - " first primary assoc: " + primaryAssoc + "\n" + - " second primary assoc: " + assoc); - } - primaryAssoc = assoc; - // we keep looping to hunt out data integrity issues - } - // did we find a primary assoc? - if (primaryAssoc == null) - { - // the only condition where this is allowed is if the given node is a root node - Store store = node.getStore(); - Node rootNode = store.getRootNode(); - if (rootNode == null) - { - // a store without a root node - the entire store is hosed - throw new DataIntegrityViolationException("Store has no root node: \n" + - " store: " + store); - } - if (!rootNode.equals(node)) - { - // it wasn't the root node - throw new DataIntegrityViolationException("Non-root node has no primary parent: \n" + - " child: " + node); - } - } - // done - return primaryAssoc; - } - - public NodeAssoc newNodeAssoc(Node sourceNode, Node targetNode, QName assocTypeQName) - { - NodeAssoc assoc = new NodeAssocImpl(); - assoc.setTypeQName(assocTypeQName); - assoc.buildAssociation(sourceNode, targetNode); - // persist - try - { - getHibernateTemplate().save(assoc); - } - catch (DataIntegrityViolationException e) - { - throw new AssociationExistsException( - sourceNode.getNodeRef(), - targetNode.getNodeRef(), - assocTypeQName, - e); - } - // done - return assoc; - } - - @SuppressWarnings("unchecked") - public List getNodeAssocsToAndFrom(final Node node) - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session - .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_NODE_ASSOCS_TO_AND_FROM) - .setLong("nodeId", node.getId()); - return query.list(); - } - }; - List results = (List) getHibernateTemplate().execute(callback); - return results; - } - - public NodeAssoc getNodeAssoc( - final Node sourceNode, - final Node targetNode, - final QName assocTypeQName) - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session - .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_NODE_ASSOC) - .setLong("sourceId", sourceNode.getId()) - .setLong("targetId", targetNode.getId()) - .setParameter("assocTypeQName", assocTypeQName); - return query.uniqueResult(); - } - }; - NodeAssoc result = (NodeAssoc) getHibernateTemplate().execute(callback); - return result; - } - - @SuppressWarnings("unchecked") - public List getTargetNodeAssocs(final Node sourceNode) - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session - .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_TARGET_ASSOCS) - .setLong("sourceId", sourceNode.getId()); - return query.list(); - } - }; - List queryResults = (List) getHibernateTemplate().execute(callback); - return queryResults; - } - - @SuppressWarnings("unchecked") - public List getSourceNodeAssocs(final Node targetNode) - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session - .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_SOURCE_ASSOCS) - .setLong("targetId", targetNode.getId()); - return query.list(); - } - }; - List queryResults = (List) getHibernateTemplate().execute(callback); - return queryResults; - } - - public void deleteNodeAssoc(NodeAssoc assoc) - { - // Remove instance - getHibernateTemplate().delete(assoc); - // Flush to ensure that the database constraints aren't violated if the assoc - // is recreated in the transaction - getSession().flush(); - } - - public List getPropertyValuesByActualType(DataTypeDefinition actualDataTypeDefinition) - { - // get the in-database string representation of the actual type - QName typeQName = actualDataTypeDefinition.getName(); - final String actualTypeString = PropertyValue.getActualTypeString(typeQName); - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session - .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_NODES_WITH_PROPERTY_VALUES_BY_ACTUAL_TYPE) - .setString("actualTypeString", actualTypeString); - return query.scroll(ScrollMode.FORWARD_ONLY); - } - }; - ScrollableResults results = (ScrollableResults) getHibernateTemplate().execute(callback); - // Loop through, extracting content URLs - List convertedValues = new ArrayList(1000); - TypeConverter converter = DefaultTypeConverter.INSTANCE; - while(results.next()) - { - Node node = (Node) results.get()[0]; - // loop through all the node properties - Map properties = node.getProperties(); - for (PropertyValue propertyValue : properties.values()) - { - // ignore nulls - if (propertyValue == null) - { - continue; - } - // Get the actual value(s) as a collection - Collection values = propertyValue.getCollection(DataTypeDefinition.ANY); - // attempt to convert instance in the collection - for (Serializable value : values) - { - // ignore nulls (null entries in collections) - if (value == null) - { - continue; - } - try - { - Serializable convertedValue = (Serializable) converter.convert(actualDataTypeDefinition, value); - // it converted, so add it - convertedValues.add(convertedValue); - } - catch (Throwable e) - { - // The value can't be converted - forget it - } - } - } - // evict all data from the session - getSession().clear(); - } - return convertedValues; - } - - /* - * Queries for transactions - */ - private static final String QUERY_GET_LAST_TXN_ID_FOR_STORE = "txn.GetLastTxnIdForStore"; - private static final String QUERY_GET_TXN_UPDATE_COUNT_FOR_STORE = "txn.GetTxnUpdateCountForStore"; - private static final String QUERY_GET_TXN_DELETE_COUNT_FOR_STORE = "txn.GetTxnDeleteCountForStore"; - private static final String QUERY_COUNT_TRANSACTIONS = "txn.CountTransactions"; - private static final String QUERY_GET_NEXT_TXNS = "txn.GetNextTxns"; - private static final String QUERY_GET_TXN_CHANGES_FOR_STORE = "txn.GetTxnChangesForStore"; - private static final String QUERY_GET_TXN_CHANGES = "txn.GetTxnChanges"; - - @SuppressWarnings("unchecked") - public Transaction getLastTxn(final StoreRef storeRef) - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session.getNamedQuery(QUERY_GET_LAST_TXN_ID_FOR_STORE); - query.setString("protocol", storeRef.getProtocol()) - .setString("identifier", storeRef.getIdentifier()) - .setMaxResults(1) - .setReadOnly(true); - return query.uniqueResult(); - } - }; - Long txnId = (Long) getHibernateTemplate().execute(callback); - Transaction txn = null; - if (txnId != null) - { - txn = (Transaction) getSession().get(TransactionImpl.class, txnId); - } - // done - return txn; - } - - @SuppressWarnings("unchecked") - public int getTxnUpdateCountForStore(final StoreRef storeRef, final long txnId) - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session.getNamedQuery(QUERY_GET_TXN_UPDATE_COUNT_FOR_STORE); - query.setLong("txnId", txnId) - .setString("protocol", storeRef.getProtocol()) - .setString("identifier", storeRef.getIdentifier()) - .setMaxResults(1) - .setReadOnly(true); - return query.uniqueResult(); - } - }; - Integer count = (Integer) getHibernateTemplate().execute(callback); - // done - return count; - } - - @SuppressWarnings("unchecked") - public int getTxnDeleteCountForStore(final StoreRef storeRef, final long txnId) - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session.getNamedQuery(QUERY_GET_TXN_DELETE_COUNT_FOR_STORE); - query.setLong("txnId", txnId) - .setString("protocol", storeRef.getProtocol()) - .setString("identifier", storeRef.getIdentifier()) - .setMaxResults(1) - .setReadOnly(true); - return query.uniqueResult(); - } - }; - Integer count = (Integer) getHibernateTemplate().execute(callback); - // done - return count; - } - - @SuppressWarnings("unchecked") - public int getTransactionCount() - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session.getNamedQuery(QUERY_COUNT_TRANSACTIONS); - query.setMaxResults(1) - .setReadOnly(true); - return query.uniqueResult(); - } - }; - Integer count = (Integer) getHibernateTemplate().execute(callback); - // done - return count.intValue(); - } - - @SuppressWarnings("unchecked") - public List getNextTxns(final Transaction lastTxn, final int count) - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - long lastTxnId = (lastTxn == null) ? -1L : lastTxn.getId(); - - Query query = session.getNamedQuery(QUERY_GET_NEXT_TXNS); - query.setLong("lastTxnId", lastTxnId) - .setMaxResults(count) - .setReadOnly(true); - return query.list(); - } - }; - List results = (List) getHibernateTemplate().execute(callback); - // done - return results; - } - - @SuppressWarnings("unchecked") - public List getTxnChangesForStore(final StoreRef storeRef, final long txnId) - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session.getNamedQuery(QUERY_GET_TXN_CHANGES_FOR_STORE); - query.setLong("txnId", txnId) - .setString("protocol", storeRef.getProtocol()) - .setString("identifier", storeRef.getIdentifier()) - .setReadOnly(true); - return query.list(); - } - }; - List results = (List) getHibernateTemplate().execute(callback); - // transform into a simpler form - List nodeRefs = new ArrayList(results.size()); - for (NodeStatus nodeStatus : results) - { - NodeRef nodeRef = new NodeRef(storeRef, nodeStatus.getKey().getGuid()); - nodeRefs.add(nodeRef); - } - // done - return nodeRefs; - } - - @SuppressWarnings("unchecked") - public List getTxnChanges(final long txnId) - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session.getNamedQuery(QUERY_GET_TXN_CHANGES); - query.setLong("txnId", txnId) - .setReadOnly(true); - return query.list(); - } - }; - List results = (List) getHibernateTemplate().execute(callback); - // transform into a simpler form - List nodeRefs = new ArrayList(results.size()); - for (NodeStatus nodeStatus : results) - { - NodeRef nodeRef = new NodeRef( - nodeStatus.getKey().getProtocol(), - nodeStatus.getKey().getIdentifier(), - nodeStatus.getKey().getGuid()); - nodeRefs.add(nodeRef); - } - // done - return nodeRefs; - } -} +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.node.db.hibernate; + +import java.io.Serializable; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.zip.CRC32; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.domain.ChildAssoc; +import org.alfresco.repo.domain.Node; +import org.alfresco.repo.domain.NodeAssoc; +import org.alfresco.repo.domain.NodeKey; +import org.alfresco.repo.domain.NodeStatus; +import org.alfresco.repo.domain.PropertyValue; +import org.alfresco.repo.domain.Server; +import org.alfresco.repo.domain.Store; +import org.alfresco.repo.domain.StoreKey; +import org.alfresco.repo.domain.Transaction; +import org.alfresco.repo.domain.hibernate.ChildAssocImpl; +import org.alfresco.repo.domain.hibernate.NodeAssocImpl; +import org.alfresco.repo.domain.hibernate.NodeImpl; +import org.alfresco.repo.domain.hibernate.NodeStatusImpl; +import org.alfresco.repo.domain.hibernate.ServerImpl; +import org.alfresco.repo.domain.hibernate.StoreImpl; +import org.alfresco.repo.domain.hibernate.TransactionImpl; +import org.alfresco.repo.node.db.NodeDaoService; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.TransactionAwareSingleton; +import org.alfresco.repo.transaction.TransactionalDao; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.InvalidTypeException; +import org.alfresco.service.cmr.repository.AssociationExistsException; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.repository.datatype.TypeConverter; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.GUID; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.hibernate.ObjectDeletedException; +import org.hibernate.Query; +import org.hibernate.ScrollMode; +import org.hibernate.ScrollableResults; +import org.hibernate.Session; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.orm.hibernate3.HibernateCallback; +import org.springframework.orm.hibernate3.support.HibernateDaoSupport; + +/** + * Hibernate-specific implementation of the persistence-independent node DAO interface + * + * @author Derek Hulley + */ +public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements NodeDaoService, TransactionalDao +{ + private static final String QUERY_GET_ALL_STORES = "store.GetAllStores"; + private static final String UPDATE_SET_CHILD_ASSOC_NAME = "node.updateChildAssocName"; + private static final String QUERY_GET_CHILD_ASSOCS = "node.GetChildAssocs"; + private static final String QUERY_GET_CHILD_ASSOC_BY_TYPE_AND_NAME = "node.GetChildAssocByTypeAndName"; + private static final String QUERY_GET_CHILD_ASSOC_REFS = "node.GetChildAssocRefs"; + private static final String QUERY_GET_NODE_ASSOC = "node.GetNodeAssoc"; + private static final String QUERY_GET_NODE_ASSOCS_TO_AND_FROM = "node.GetNodeAssocsToAndFrom"; + private static final String QUERY_GET_TARGET_ASSOCS = "node.GetTargetAssocs"; + private static final String QUERY_GET_SOURCE_ASSOCS = "node.GetSourceAssocs"; + private static final String QUERY_GET_NODES_WITH_PROPERTY_VALUES_BY_ACTUAL_TYPE = "node.GetNodesWithPropertyValuesByActualType"; + private static final String QUERY_GET_SERVER_BY_IPADDRESS = "server.getServerByIpAddress"; + + private static Log logger = LogFactory.getLog(HibernateNodeDaoServiceImpl.class); + + /** a uuid identifying this unique instance */ + private final String uuid; + + private static TransactionAwareSingleton serverIdSingleton = new TransactionAwareSingleton(); + private final String ipAddress; + + /** + * + */ + public HibernateNodeDaoServiceImpl() + { + this.uuid = GUID.generate(); + try + { + ipAddress = InetAddress.getLocalHost().getHostAddress(); + } + catch (UnknownHostException e) + { + throw new AlfrescoRuntimeException("Failed to get server IP address", e); + } + } + + /** + * Checks equality by type and uuid + */ + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + else if (!(obj instanceof HibernateNodeDaoServiceImpl)) + { + return false; + } + HibernateNodeDaoServiceImpl that = (HibernateNodeDaoServiceImpl) obj; + return this.uuid.equals(that.uuid); + } + + /** + * @see #uuid + */ + public int hashCode() + { + return uuid.hashCode(); + } + + /** + * Gets/creates the server instance to use for the life of this instance + */ + private Server getServer() + { + Long serverId = serverIdSingleton.get(); + Server server = null; + if (serverId != null) + { + server = (Server) getSession().get(ServerImpl.class, serverId); + if (server != null) + { + return server; + } + } + try + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_SERVER_BY_IPADDRESS) + .setString("ipAddress", ipAddress); + return query.uniqueResult(); + } + }; + server = (Server) getHibernateTemplate().execute(callback); + // create it if it doesn't exist + if (server == null) + { + server = new ServerImpl(); + server.setIpAddress(ipAddress); + try + { + getSession().save(server); + } + catch (DataIntegrityViolationException e) + { + // get it again + server = (Server) getHibernateTemplate().execute(callback); + if (server == null) + { + throw new AlfrescoRuntimeException("Unable to create server instance: " + ipAddress); + } + } + } + // push the value into the singleton + serverIdSingleton.put(server.getId()); + + return server; + } + catch (Exception e) + { + throw new AlfrescoRuntimeException("Failed to create server instance", e); + } + } + + private static final String RESOURCE_KEY_TRANSACTION_ID = "hibernate.transaction.id"; + private Transaction getCurrentTransaction() + { + Transaction transaction = null; + Serializable txnId = (Serializable) AlfrescoTransactionSupport.getResource(RESOURCE_KEY_TRANSACTION_ID); + if (txnId == null) + { + // no transaction instance has been bound to the transaction + transaction = new TransactionImpl(); + transaction.setChangeTxnId(AlfrescoTransactionSupport.getTransactionId()); + transaction.setServer(getServer()); + txnId = getHibernateTemplate().save(transaction); + // bind the id + AlfrescoTransactionSupport.bindResource(RESOURCE_KEY_TRANSACTION_ID, txnId); + } + else + { + transaction = (Transaction) getHibernateTemplate().get(TransactionImpl.class, txnId); + } + return transaction; + } + + /** + * Does this Session contain any changes which must be + * synchronized with the store? + * + * @return true => changes are pending + */ + public boolean isDirty() + { + // create a callback for the task + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + return session.isDirty(); + } + }; + // execute the callback + return ((Boolean)getHibernateTemplate().execute(callback)).booleanValue(); + } + + /** + * Just flushes the session + */ + public void flush() + { + getSession().flush(); + } + + /** + * @see #QUERY_GET_ALL_STORES + */ + @SuppressWarnings("unchecked") + public List getStores() + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session.getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_ALL_STORES); + return query.list(); + } + }; + List queryResults = (List) getHibernateTemplate().execute(callback); + // done + return queryResults; + } + + /** + * Ensures that the store protocol/identifier combination is unique + */ + public Store createStore(String protocol, String identifier) + { + // ensure that the name isn't in use + Store store = getStore(protocol, identifier); + if (store != null) + { + throw new RuntimeException("A store already exists: \n" + + " protocol: " + protocol + "\n" + + " identifier: " + identifier + "\n" + + " store: " + store); + } + + store = new StoreImpl(); + // set key + store.setKey(new StoreKey(protocol, identifier)); + // persist so that it is present in the hibernate cache + getHibernateTemplate().save(store); + // create and assign a root node + Node rootNode = newNode( + store, + GUID.generate(), + ContentModel.TYPE_STOREROOT); + store.setRootNode(rootNode); + // done + return store; + } + + public Store getStore(String protocol, String identifier) + { + StoreKey storeKey = new StoreKey(protocol, identifier); + Store store = (Store) getHibernateTemplate().get(StoreImpl.class, storeKey); + // done + return store; + } + + /** + * Fetch the node status, if it exists + */ + public NodeStatus getNodeStatus(NodeRef nodeRef, boolean update) + { + NodeKey nodeKey = new NodeKey(nodeRef); + NodeStatus status = null; + try + { + status = (NodeStatus) getHibernateTemplate().get(NodeStatusImpl.class, nodeKey); + } + catch (DataAccessException e) + { + if (e.contains(ObjectDeletedException.class)) + { + // the object no longer exists + return null; + } + throw e; + } + // create if necessary + if (status == null && update) + { + status = new NodeStatusImpl(); + status.setKey(nodeKey); + status.setTransaction(getCurrentTransaction()); + getHibernateTemplate().save(status); + } + else if (status != null && update) + { + // update the transaction + status.setTransaction(getCurrentTransaction()); + } + // done + return status; + } + + public void recordChangeId(NodeRef nodeRef) + { + NodeKey key = new NodeKey(nodeRef); + + NodeStatus status = (NodeStatus) getHibernateTemplate().get(NodeStatusImpl.class, key); + if (status == null) + { + // the node never existed or the status was deleted + return; + } + else + { + status.getTransaction().setChangeTxnId(AlfrescoTransactionSupport.getTransactionId()); + } + } + + public Node newNode(Store store, String uuid, QName nodeTypeQName) throws InvalidTypeException + { + NodeKey key = new NodeKey(store.getKey(), uuid); + + // create (or reuse) the mandatory node status + NodeStatus status = (NodeStatus) getHibernateTemplate().get(NodeStatusImpl.class, key); + if (status == null) + { + status = new NodeStatusImpl(); + status.setKey(key); + } + else + { + // The node existed at some point. + // Although unlikely, it is possible that the node was deleted in this transaction. + // If that is the case, then the session has to be flushed so that the database + // constraints aren't violated as the node creation will write to the database to + // get an ID + if (status.getTransaction().getChangeTxnId().equals(AlfrescoTransactionSupport.getTransactionId())) + { + // flush + getHibernateTemplate().flush(); + } + } + + // build a concrete node based on a bootstrap type + Node node = new NodeImpl(); + // set other required properties + node.setStore(store); + node.setUuid(uuid); + node.setTypeQName(nodeTypeQName); + // persist the node + getHibernateTemplate().save(node); + + // set required status properties + status.setNode(node); + // assign a transaction + if (status.getTransaction() == null) + { + status.setTransaction(getCurrentTransaction()); + } + // persist the nodestatus + getHibernateTemplate().save(status); + + // done + return node; + } + + public Node getNode(NodeRef nodeRef) + { + // get it via the node status + NodeStatus status = getNodeStatus(nodeRef, false); + if (status == null) + { + // no status implies no node + return null; + } + else + { + // a status may have a node + Node node = status.getNode(); + return node; + } + } + + /** + * Manually ensures that all cascading of associations is taken care of + */ + public void deleteNode(Node node, boolean cascade) + { + Set deletedChildAssocIds = new HashSet(10); + deleteNodeInternal(node, cascade, deletedChildAssocIds); + } + + /** + * + * @param node + * @param cascade true to cascade delete + * @param deletedChildAssocIds previously deleted child associations + */ + private void deleteNodeInternal(Node node, boolean cascade, Set deletedChildAssocIds) + { + // delete all parent assocs + if (logger.isDebugEnabled()) + { + logger.debug("Deleting parent assocs of node " + node.getId()); + } + + Collection parentAssocs = node.getParentAssocs(); + parentAssocs = new ArrayList(parentAssocs); + for (ChildAssoc assoc : parentAssocs) + { + deleteChildAssocInternal(assoc, false, deletedChildAssocIds); // we don't cascade upwards + } + // delete all child assocs + if (logger.isDebugEnabled()) + { + logger.debug("Deleting child assocs of node " + node.getId()); + } + Collection childAssocs = getChildAssocs(node); + childAssocs = new ArrayList(childAssocs); + for (ChildAssoc assoc : childAssocs) + { + deleteChildAssocInternal(assoc, cascade, deletedChildAssocIds); // potentially cascade downwards + } + // delete all node associations to and from + if (logger.isDebugEnabled()) + { + logger.debug("Deleting source and target assocs of node " + node.getId()); + } + List nodeAssocs = getNodeAssocsToAndFrom(node); + for (NodeAssoc assoc : nodeAssocs) + { + getHibernateTemplate().delete(assoc); + } + // update the node status + NodeRef nodeRef = node.getNodeRef(); + NodeStatus nodeStatus = getNodeStatus(nodeRef, true); + nodeStatus.setNode(null); + nodeStatus.getTransaction().setChangeTxnId(AlfrescoTransactionSupport.getTransactionId()); + // finally delete the node + getHibernateTemplate().delete(node); + // flush to ensure constraints can't be violated + getSession().flush(); + // done + } + + private long getCrc(String str) + { + CRC32 crc = new CRC32(); + crc.update(str.getBytes()); + return crc.getValue(); + } + + private static final String TRUNCATED_NAME_INDICATOR = "~~~"; + private String getShortName(String str) + { + int length = str.length(); + if (length <= 50) + { + return str; + } + else + { + StringBuilder ret = new StringBuilder(50); + ret.append(str.substring(0, 47)).append(TRUNCATED_NAME_INDICATOR); + return ret.toString(); + } + } + + public ChildAssoc newChildAssoc( + Node parentNode, + Node childNode, + boolean isPrimary, + QName assocTypeQName, + QName qname) + { + /* + * This initial child association creation will fail IFF there is already + * an association of the given type and name between the two nodes. For new association + * creation, this can only occur if two transactions attempt to create a secondary + * child association between the same two nodes. As this is unlikely, it is + * appropriate to just throw a runtime exception and let the second transaction + * fail. + * + * We don't need to flush the session beforehand as there won't be any deletions + * of the assocation pending. The association deletes, on the other hand, have + * to flush early in order to ensure that the database unique index isn't violated + * if the association is recreated subsequently. + */ + + // assign a random name to the node + String randomName = GUID.generate(); + + ChildAssoc assoc = new ChildAssocImpl(); + assoc.setTypeQName(assocTypeQName); + assoc.setChildNodeName(randomName); + assoc.setChildNodeNameCrc(-1L); // random names compete only with each other + assoc.setQname(qname); + assoc.setIsPrimary(isPrimary); + // maintain inverse sets + assoc.buildAssociation(parentNode, childNode); + // persist it + getHibernateTemplate().save(assoc); + // done + return assoc; + } + + public void setChildNameUnique(final ChildAssoc childAssoc, String childName) + { + /* + * As the Hibernate session is rendered useless when an exception is + * bubbled up, we go direct to the database to update the child association. + * This preserves the session and client code can catch the resulting + * exception and react to it whilst in the same transaction. + * + * We ensure that case-insensitivity is maintained by persisting + * the lowercase version of the child node name. + */ + + String childNameNew = null; + long crc = -1; + if (childName == null) + { + // random names compete only with each other, i.e. not at all + childNameNew = GUID.generate(); + crc = -1; + } + else + { + // assigned names compete exactly + childNameNew = childName.toLowerCase(); + crc = getCrc(childNameNew); + } + + final String childNameNewShort = getShortName(childNameNew); + final long childNameNewCrc = crc; + + // check if the name has changed + if (childAssoc.getChildNodeNameCrc() == childNameNewCrc) + { + if (childAssoc.getChildNodeName().equals(childNameNewShort)) + { + // nothing changed + return; + } + } + + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + session.flush(); + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.UPDATE_SET_CHILD_ASSOC_NAME) + .setString("newName", childNameNewShort) + .setLong("newNameCrc", childNameNewCrc) + .setLong("childAssocId", childAssoc.getId()); + return (Integer) query.executeUpdate(); + } + }; + try + { + Integer count = (Integer) getHibernateTemplate().execute(callback); + // refresh the entity directly + if (count.intValue() == 0) + { + if (logger.isDebugEnabled()) + { + logger.debug("ChildAssoc not updated: " + childAssoc.getId()); + } + } + else + { + getHibernateTemplate().refresh(childAssoc); + } + } + catch (DataIntegrityViolationException e) + { + NodeRef parentNodeRef = childAssoc.getParent().getNodeRef(); + QName assocTypeQName = childAssoc.getTypeQName(); + throw new DuplicateChildNodeNameException(parentNodeRef, assocTypeQName, childName); + } + } + + @SuppressWarnings("unchecked") + public Collection getChildAssocs(final Node parentNode) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOCS) + .setLong("parentId", parentNode.getId()); + return query.list(); + } + }; + List queryResults = (List) getHibernateTemplate().execute(callback); + return queryResults; + } + + @SuppressWarnings("unchecked") + public Collection getChildAssocRefs(final Node parentNode) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOC_REFS) + .setLong("parentId", parentNode.getId()); + return query.list(); + } + }; + List queryResults = (List) getHibernateTemplate().execute(callback); + Collection refs = new ArrayList(queryResults.size()); + NodeRef parentNodeRef = parentNode.getNodeRef(); + for (Object[] row : queryResults) + { + String childProtocol = (String) row[5]; + String childIdentifier = (String) row[6]; + String childUuid = (String) row[7]; + NodeRef childNodeRef = new NodeRef(new StoreRef(childProtocol, childIdentifier), childUuid); + QName assocTypeQName = (QName) row[0]; + QName assocQName = (QName) row[1]; + Boolean assocIsPrimary = (Boolean) row[2]; + Integer assocIndex = (Integer) row[3]; + ChildAssociationRef assocRef = new ChildAssociationRef( + assocTypeQName, + parentNodeRef, + assocQName, + childNodeRef, + assocIsPrimary.booleanValue(), + assocIndex.intValue()); + refs.add(assocRef); + } + return refs; + } + + public ChildAssoc getChildAssoc( + Node parentNode, + Node childNode, + QName assocTypeQName, + QName qname) + { + ChildAssociationRef childAssocRef = new ChildAssociationRef( + assocTypeQName, + parentNode.getNodeRef(), + qname, + childNode.getNodeRef()); + // get all the parent's child associations + Collection assocs = getChildAssocs(parentNode); + // hunt down the desired assoc + for (ChildAssoc assoc : assocs) + { + // is it a match? + if (!assoc.getChildAssocRef().equals(childAssocRef)) // not a match + { + continue; + } + else + { + return assoc; + } + } + // not found + return null; + } + + public ChildAssoc getChildAssoc(final Node parentNode, final QName assocTypeQName, final String childName) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + String childNameLower = childName.toLowerCase(); + String childNameShort = getShortName(childNameLower); + long childNameLowerCrc = getCrc(childNameLower); + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOC_BY_TYPE_AND_NAME) + .setLong("parentId", parentNode.getId()) + .setParameter("typeQName", assocTypeQName) + .setParameter("childNodeName", childNameShort) + .setLong("childNodeNameCrc", childNameLowerCrc); + return query.uniqueResult(); + } + }; + ChildAssoc childAssoc = (ChildAssoc) getHibernateTemplate().execute(callback); + return childAssoc; + } + + /** + * Public level entry-point. + */ + public void deleteChildAssoc(ChildAssoc assoc, boolean cascade) + { + Set deletedChildAssocIds = new HashSet(10); + deleteChildAssocInternal(assoc, cascade, deletedChildAssocIds); + } + + /** + * Cascade deletion of child associations, recording the IDs of deleted assocs. + * + * @param assoc the association to delete + * @param cascade true to cascade to the child node of the association + * @param deletedChildAssocIds already-deleted associations + */ + private void deleteChildAssocInternal(ChildAssoc assoc, boolean cascade, Set deletedChildAssocIds) + { + Long childAssocId = assoc.getId(); + if (deletedChildAssocIds.contains(childAssocId)) + { + if (logger.isDebugEnabled()) + { + logger.debug("Ignoring parent-child association " + assoc.getId()); + } + return; + } + + if (logger.isDebugEnabled()) + { + logger.debug( + "Deleting parent-child association " + assoc.getId() + + (cascade ? " with" : " without") + " cascade:" + + assoc.getParent().getId() + " -> " + assoc.getChild().getId()); + } + + Node childNode = assoc.getChild(); + + // maintain inverse association sets + assoc.removeAssociation(); + // remove instance + getHibernateTemplate().delete(assoc); + deletedChildAssocIds.add(childAssocId); // ensure that we don't attempt to delete it twice + + if (cascade && assoc.getIsPrimary()) // the assoc is primary + { + // delete the child node + deleteNodeInternal(childNode, cascade, deletedChildAssocIds); + /* + * The child node deletion will cascade delete all assocs to + * and from it, but we have safely removed this one, so no + * duplicate call will be received to do this + */ + } + + // To ensure the validity of the constraint enforcement by the database, + // we have to flush here + getSession().flush(); + } + + public ChildAssoc getPrimaryParentAssoc(Node node) + { + // get the assocs pointing to the node + Collection parentAssocs = node.getParentAssocs(); + ChildAssoc primaryAssoc = null; + for (ChildAssoc assoc : parentAssocs) + { + // ignore non-primary assocs + if (!assoc.getIsPrimary()) + { + continue; + } + else if (primaryAssoc != null) + { + // we have more than one somehow + throw new DataIntegrityViolationException( + "Multiple primary associations: \n" + + " child: " + node + "\n" + + " first primary assoc: " + primaryAssoc + "\n" + + " second primary assoc: " + assoc); + } + primaryAssoc = assoc; + // we keep looping to hunt out data integrity issues + } + // did we find a primary assoc? + if (primaryAssoc == null) + { + // the only condition where this is allowed is if the given node is a root node + Store store = node.getStore(); + Node rootNode = store.getRootNode(); + if (rootNode == null) + { + // a store without a root node - the entire store is hosed + throw new DataIntegrityViolationException("Store has no root node: \n" + + " store: " + store); + } + if (!rootNode.equals(node)) + { + // it wasn't the root node + throw new DataIntegrityViolationException("Non-root node has no primary parent: \n" + + " child: " + node); + } + } + // done + return primaryAssoc; + } + + public NodeAssoc newNodeAssoc(Node sourceNode, Node targetNode, QName assocTypeQName) + { + NodeAssoc assoc = new NodeAssocImpl(); + assoc.setTypeQName(assocTypeQName); + assoc.buildAssociation(sourceNode, targetNode); + // persist + try + { + getHibernateTemplate().save(assoc); + } + catch (DataIntegrityViolationException e) + { + throw new AssociationExistsException( + sourceNode.getNodeRef(), + targetNode.getNodeRef(), + assocTypeQName, + e); + } + // done + return assoc; + } + + @SuppressWarnings("unchecked") + public List getNodeAssocsToAndFrom(final Node node) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_NODE_ASSOCS_TO_AND_FROM) + .setLong("nodeId", node.getId()); + return query.list(); + } + }; + List results = (List) getHibernateTemplate().execute(callback); + return results; + } + + public NodeAssoc getNodeAssoc( + final Node sourceNode, + final Node targetNode, + final QName assocTypeQName) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_NODE_ASSOC) + .setLong("sourceId", sourceNode.getId()) + .setLong("targetId", targetNode.getId()) + .setParameter("assocTypeQName", assocTypeQName); + return query.uniqueResult(); + } + }; + NodeAssoc result = (NodeAssoc) getHibernateTemplate().execute(callback); + return result; + } + + @SuppressWarnings("unchecked") + public List getTargetNodeAssocs(final Node sourceNode) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_TARGET_ASSOCS) + .setLong("sourceId", sourceNode.getId()); + return query.list(); + } + }; + List queryResults = (List) getHibernateTemplate().execute(callback); + return queryResults; + } + + @SuppressWarnings("unchecked") + public List getSourceNodeAssocs(final Node targetNode) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_SOURCE_ASSOCS) + .setLong("targetId", targetNode.getId()); + return query.list(); + } + }; + List queryResults = (List) getHibernateTemplate().execute(callback); + return queryResults; + } + + public void deleteNodeAssoc(NodeAssoc assoc) + { + // Remove instance + getHibernateTemplate().delete(assoc); + // Flush to ensure that the database constraints aren't violated if the assoc + // is recreated in the transaction + getSession().flush(); + } + + public List getPropertyValuesByActualType(DataTypeDefinition actualDataTypeDefinition) + { + // get the in-database string representation of the actual type + QName typeQName = actualDataTypeDefinition.getName(); + final String actualTypeString = PropertyValue.getActualTypeString(typeQName); + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_NODES_WITH_PROPERTY_VALUES_BY_ACTUAL_TYPE) + .setString("actualTypeString", actualTypeString); + return query.scroll(ScrollMode.FORWARD_ONLY); + } + }; + ScrollableResults results = (ScrollableResults) getHibernateTemplate().execute(callback); + // Loop through, extracting content URLs + List convertedValues = new ArrayList(1000); + TypeConverter converter = DefaultTypeConverter.INSTANCE; + while(results.next()) + { + Node node = (Node) results.get()[0]; + // loop through all the node properties + Map properties = node.getProperties(); + for (PropertyValue propertyValue : properties.values()) + { + // ignore nulls + if (propertyValue == null) + { + continue; + } + // Get the actual value(s) as a collection + Collection values = propertyValue.getCollection(DataTypeDefinition.ANY); + // attempt to convert instance in the collection + for (Serializable value : values) + { + // ignore nulls (null entries in collections) + if (value == null) + { + continue; + } + try + { + Serializable convertedValue = (Serializable) converter.convert(actualDataTypeDefinition, value); + // it converted, so add it + convertedValues.add(convertedValue); + } + catch (Throwable e) + { + // The value can't be converted - forget it + } + } + } + // evict all data from the session + getSession().clear(); + } + return convertedValues; + } + + /* + * Queries for transactions + */ + private static final String QUERY_GET_LAST_TXN_ID = "txn.GetLastTxnId"; + private static final String QUERY_GET_LAST_TXN_ID_FOR_STORE = "txn.GetLastTxnIdForStore"; + private static final String QUERY_GET_TXN_UPDATE_COUNT_FOR_STORE = "txn.GetTxnUpdateCountForStore"; + private static final String QUERY_GET_TXN_DELETE_COUNT_FOR_STORE = "txn.GetTxnDeleteCountForStore"; + private static final String QUERY_COUNT_TRANSACTIONS = "txn.CountTransactions"; + private static final String QUERY_GET_NEXT_TXNS = "txn.GetNextTxns"; + private static final String QUERY_GET_NEXT_REMOTE_TXNS = "txn.GetNextRemoteTxns"; + private static final String QUERY_GET_TXN_CHANGES_FOR_STORE = "txn.GetTxnChangesForStore"; + private static final String QUERY_GET_TXN_CHANGES = "txn.GetTxnChanges"; + + public Transaction getTxnById(long txnId) + { + return (Transaction) getSession().get(TransactionImpl.class, new Long(txnId)); + } + + @SuppressWarnings("unchecked") + public Transaction getLastTxn() + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session.getNamedQuery(QUERY_GET_LAST_TXN_ID); + query.setMaxResults(1) + .setReadOnly(true); + return query.uniqueResult(); + } + }; + Long txnId = (Long) getHibernateTemplate().execute(callback); + Transaction txn = null; + if (txnId != null) + { + txn = (Transaction) getSession().get(TransactionImpl.class, txnId); + } + // done + return txn; + } + + @SuppressWarnings("unchecked") + public Transaction getLastTxnForStore(final StoreRef storeRef) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session.getNamedQuery(QUERY_GET_LAST_TXN_ID_FOR_STORE); + query.setString("protocol", storeRef.getProtocol()) + .setString("identifier", storeRef.getIdentifier()) + .setMaxResults(1) + .setReadOnly(true); + return query.uniqueResult(); + } + }; + Long txnId = (Long) getHibernateTemplate().execute(callback); + Transaction txn = null; + if (txnId != null) + { + txn = (Transaction) getSession().get(TransactionImpl.class, txnId); + } + // done + return txn; + } + + @SuppressWarnings("unchecked") + public int getTxnUpdateCount(final long txnId) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session.getNamedQuery(QUERY_GET_TXN_UPDATE_COUNT_FOR_STORE); + query.setLong("txnId", txnId) + .setReadOnly(true); + return query.uniqueResult(); + } + }; + Integer count = (Integer) getHibernateTemplate().execute(callback); + // done + return count; + } + + @SuppressWarnings("unchecked") + public int getTxnDeleteCount(final long txnId) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session.getNamedQuery(QUERY_GET_TXN_DELETE_COUNT_FOR_STORE); + query.setLong("txnId", txnId) + .setReadOnly(true); + return query.uniqueResult(); + } + }; + Integer count = (Integer) getHibernateTemplate().execute(callback); + // done + return count; + } + + @SuppressWarnings("unchecked") + public int getTransactionCount() + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session.getNamedQuery(QUERY_COUNT_TRANSACTIONS); + query.setMaxResults(1) + .setReadOnly(true); + return query.uniqueResult(); + } + }; + Integer count = (Integer) getHibernateTemplate().execute(callback); + // done + return count.intValue(); + } + + @SuppressWarnings("unchecked") + public List getNextTxns(final long lastTxnId, final int count) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session.getNamedQuery(QUERY_GET_NEXT_TXNS); + query.setLong("lastTxnId", lastTxnId) + .setMaxResults(count) + .setReadOnly(true); + return query.list(); + } + }; + List results = (List) getHibernateTemplate().execute(callback); + // done + return results; + } + + @SuppressWarnings("unchecked") + public List getNextRemoteTxns(final long lastTxnId, final int count) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session.getNamedQuery(QUERY_GET_NEXT_REMOTE_TXNS); + query.setLong("lastTxnId", lastTxnId) + .setString("serverIpAddress", ipAddress) + .setMaxResults(count) + .setReadOnly(true); + return query.list(); + } + }; + List results = (List) getHibernateTemplate().execute(callback); + // done + return results; + } + + @SuppressWarnings("unchecked") + public List getTxnChangesForStore(final StoreRef storeRef, final long txnId) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session.getNamedQuery(QUERY_GET_TXN_CHANGES_FOR_STORE); + query.setLong("txnId", txnId) + .setString("protocol", storeRef.getProtocol()) + .setString("identifier", storeRef.getIdentifier()) + .setReadOnly(true); + return query.list(); + } + }; + List results = (List) getHibernateTemplate().execute(callback); + // transform into a simpler form + List nodeRefs = new ArrayList(results.size()); + for (NodeStatus nodeStatus : results) + { + NodeRef nodeRef = new NodeRef(storeRef, nodeStatus.getKey().getGuid()); + nodeRefs.add(nodeRef); + } + // done + return nodeRefs; + } + + @SuppressWarnings("unchecked") + public List getTxnChanges(final long txnId) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session.getNamedQuery(QUERY_GET_TXN_CHANGES); + query.setLong("txnId", txnId) + .setReadOnly(true); + return query.list(); + } + }; + List results = (List) getHibernateTemplate().execute(callback); + // transform into a simpler form + List nodeRefs = new ArrayList(results.size()); + for (NodeStatus nodeStatus : results) + { + NodeRef nodeRef = new NodeRef( + nodeStatus.getKey().getProtocol(), + nodeStatus.getKey().getIdentifier(), + nodeStatus.getKey().getGuid()); + nodeRefs.add(nodeRef); + } + // done + return nodeRefs; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java b/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java index c715264264..2d4f597540 100644 --- a/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java +++ b/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java @@ -16,20 +16,30 @@ */ package org.alfresco.repo.node.index; +import java.util.List; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import net.sf.acegisecurity.Authentication; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.domain.Transaction; import org.alfresco.repo.node.db.NodeDaoService; import org.alfresco.repo.search.Indexer; +import org.alfresco.repo.search.impl.lucene.LuceneQueryParser; import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.TransactionComponent; import org.alfresco.repo.transaction.TransactionUtil; import org.alfresco.repo.transaction.TransactionUtil.TransactionWork; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.NodeRef.Status; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.util.PropertyCheck; import org.alfresco.util.VmShutdownListener; @@ -224,4 +234,229 @@ public abstract class AbstractReindexComponent implements IndexRecovery } } } + + /** + * Gets the last indexed transaction working back from the provided index. + * This method can be used to hunt for a starting point for indexing of + * transactions not yet in the index. + */ + protected long getLastIndexedTxn(long lastTxnId) + { + // get the last transaction + long lastFoundTxnId = lastTxnId + 10L; + boolean found = false; + while (!found && lastFoundTxnId >= 0) + { + // reduce the transaction ID + lastFoundTxnId = lastFoundTxnId - 10L; + // break out as soon as we find a transaction that is in the index + found = isTxnIdPresentInIndex(lastFoundTxnId); + if (found) + { + break; + } + } + // done + if (logger.isDebugEnabled()) + { + logger.debug("Found last index txn before " + lastTxnId + ": " + lastFoundTxnId); + } + return lastFoundTxnId; + } + + protected boolean isTxnIdPresentInIndex(long txnId) + { + if (logger.isDebugEnabled()) + { + logger.debug("Checking for transaction in index: " + txnId); + } + + Transaction txn = nodeDaoService.getTxnById(txnId); + if (txn == null) + { + return true; + } + + // count the changes in the transaction + int updateCount = nodeDaoService.getTxnUpdateCount(txnId); + int deleteCount = nodeDaoService.getTxnDeleteCount(txnId); + if (logger.isDebugEnabled()) + { + logger.debug("Transaction has " + updateCount + " updates and " + deleteCount + " deletes: " + txnId); + } + + // get the stores + boolean found = false; + List storeRefs = nodeService.getStores(); + for (StoreRef storeRef : storeRefs) + { + boolean inStore = isTxnIdPresentInIndex(storeRef, txn, updateCount, deleteCount); + if (inStore) + { + // found in a particular store + found = true; + break; + } + } + // done + if (logger.isDebugEnabled()) + { + logger.debug("Transaction " + txnId + " was " + (found ? "found" : "not found") + " in indexes."); + } + return found; + } + + /** + * @return Returns true if the given transaction is indexed in the in the + */ + private boolean isTxnIdPresentInIndex(StoreRef storeRef, Transaction txn, int updateCount, int deleteCount) + { + long txnId = txn.getId(); + String changeTxnId = txn.getChangeTxnId(); + // do the most update check, which is most common + if (updateCount > 0) + { + ResultSet results = null; + try + { + SearchParameters sp = new SearchParameters(); + sp.addStore(storeRef); + // search for it in the index, sorting with youngest first, fetching only 1 + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery("TX:" + LuceneQueryParser.escape(changeTxnId)); + sp.setLimit(1); + + results = searcher.query(sp); + + if (results.length() > 0) + { + if (logger.isDebugEnabled()) + { + logger.debug("Index has results for txn (OK): " + txnId); + } + return true; // there were updates/creates and results for the txn were found + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("Index has no results for txn (Index out of date): " + txnId); + } + return false; + } + } + finally + { + if (results != null) { results.close(); } + } + } + // there have been deletes, so we have to ensure that none of the nodes deleted are present in the index + // get all node refs for the transaction + List nodeRefs = nodeDaoService.getTxnChangesForStore(storeRef, txnId); + for (NodeRef nodeRef : nodeRefs) + { + if (logger.isDebugEnabled()) + { + logger.debug("Searching for node in index: \n" + + " node: " + nodeRef + "\n" + + " txn: " + txnId); + } + // we know that these are all deletions + ResultSet results = null; + try + { + SearchParameters sp = new SearchParameters(); + sp.addStore(storeRef); + // search for it in the index, sorting with youngest first, fetching only 1 + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery("ID:" + LuceneQueryParser.escape(nodeRef.toString())); + sp.setLimit(1); + + results = searcher.query(sp); + + if (results.length() == 0) + { + // no results, as expected + if (logger.isDebugEnabled()) + { + logger.debug(" --> Node not found (OK)"); + } + continue; + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug(" --> Node found (Index out of date)"); + } + return false; + } + } + finally + { + if (results != null) { results.close(); } + } + } + + // all tests passed + if (logger.isDebugEnabled()) + { + logger.debug("Index is in synch with transaction: " + txnId); + } + return true; + } + + /** + * Perform a full reindexing of the given transaction in the context of a completely + * new transaction. + * + * @param txnId the transaction identifier + */ + protected void reindexTransaction(final long txnId) + { + if (logger.isDebugEnabled()) + { + logger.debug("Reindexing transaction: " + txnId); + } + + TransactionWork reindexWork = new TransactionWork() + { + public Object doWork() throws Exception + { + // get the node references pertinent to the transaction + List nodeRefs = nodeDaoService.getTxnChanges(txnId); + // reindex each node + for (NodeRef nodeRef : nodeRefs) + { + Status nodeStatus = nodeService.getNodeStatus(nodeRef); + if (nodeStatus == null) + { + // it's not there any more + continue; + } + if (nodeStatus.isDeleted()) // node deleted + { + // only the child node ref is relevant + ChildAssociationRef assocRef = new ChildAssociationRef( + ContentModel.ASSOC_CHILDREN, + null, + null, + nodeRef); + indexer.deleteNode(assocRef); + } + else // node created + { + // get the primary assoc for the node + ChildAssociationRef primaryAssocRef = nodeService.getPrimaryParent(nodeRef); + // reindex + indexer.createNode(primaryAssocRef); + } + } + // done + return null; + } + }; + TransactionUtil.executeInNonPropagatingUserTransaction(transactionService, reindexWork, true); + // done + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java b/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java index e67c1648bf..f7fc4047f1 100644 --- a/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java +++ b/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java @@ -21,27 +21,25 @@ import java.util.List; import org.alfresco.i18n.I18NUtil; import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.Transaction; -import org.alfresco.repo.search.impl.lucene.LuceneQueryParser; import org.alfresco.repo.transaction.TransactionUtil; import org.alfresco.repo.transaction.TransactionUtil.TransactionWork; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.NodeRef.Status; -import org.alfresco.service.cmr.search.ResultSet; -import org.alfresco.service.cmr.search.SearchParameters; -import org.alfresco.service.cmr.search.SearchService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** - * Component to check and recover the indexes. + * Component to check and recover the indexes. By default, the server is + * put into read-only mode during the reindex process in order to prevent metadata changes. + * This is not critical and can be {@link #setLockServer(boolean) switched off} if the + * server is required immediately. * * @author Derek Hulley */ public class FullIndexRecoveryComponent extends AbstractReindexComponent { - private static final String ERR_STORE_NOT_UP_TO_DATE = "index.recovery.store_not_up_to_date"; + private static final String ERR_INDEX_OUT_OF_DATE = "index.recovery.out_of_date"; private static final String MSG_RECOVERY_STARTING = "index.recovery.starting"; private static final String MSG_RECOVERY_COMPLETE = "index.recovery.complete"; private static final String MSG_RECOVERY_PROGRESS = "index.recovery.progress"; @@ -51,17 +49,25 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent public static enum RecoveryMode { - /** Do nothing - not even a check */ + /** Do nothing - not even a check. */ NONE, - /** Perform a quick check on the state of the indexes only */ + /** + * Perform a quick check on the state of the indexes only. + */ VALIDATE, - /** Performs a quick validation and then starts a full pass-through on failure */ + /** + * Performs a validation and starts a quick recovery, if necessary. + */ AUTO, - /** Performs a full pass-through of all recorded transactions to ensure that the indexes are up to date */ + /** + * Performs a full pass-through of all recorded transactions to ensure that the indexes + * are up to date. + */ FULL; } private RecoveryMode recoveryMode; + private boolean lockServer; public FullIndexRecoveryComponent() { @@ -69,7 +75,8 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent } /** - * Set the type of recovery to perform. + * Set the type of recovery to perform. Default is {@link RecoveryMode#VALIDATE to validate} + * the indexes only. * * @param recoveryMode one of the {@link RecoveryMode } values */ @@ -77,7 +84,18 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent { this.recoveryMode = RecoveryMode.valueOf(recoveryMode); } - + + /** + * Set this on to put the server into READ-ONLY mode for the duration of the index recovery. + * The default is true, i.e. the server will be locked against further updates. + * + * @param lockServer true to force the server to be read-only + */ + public void setLockServer(boolean lockServer) + { + this.lockServer = lockServer; + } + @Override protected void reindexImpl() { @@ -99,25 +117,22 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent } else // validate first { - List storeRefs = nodeService.getStores(); - for (StoreRef storeRef : storeRefs) + Transaction txn = nodeDaoService.getLastTxn(); + if (txn == null) { - // get the last txn ID in the database - Transaction txn = nodeDaoService.getLastTxn(storeRef); - boolean lastChangeTxnIdInIndex = isTxnIdPresentInIndex(storeRef, txn); - if (lastChangeTxnIdInIndex) - { - // this store is good - continue; - } - // this store isn't up to date - String msg = I18NUtil.getMessage(ERR_STORE_NOT_UP_TO_DATE, storeRef); + // no transactions - just bug out + return; + } + long txnId = txn.getId(); + boolean txnInIndex = isTxnIdPresentInIndex(txnId); + if (!txnInIndex) + { + String msg = I18NUtil.getMessage(ERR_INDEX_OUT_OF_DATE); logger.warn(msg); - // the store is out of date - validation failed + // this store isn't up to date if (recoveryMode == RecoveryMode.VALIDATE) { - // next store - continue; + // the store is out of date - validation failed } else if (recoveryMode == RecoveryMode.AUTO) { @@ -130,8 +145,11 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent boolean allowWrite = !transactionService.isReadOnly(); try { - // set the server into read-only mode - transactionService.setAllowWrite(false); + if (lockServer) + { + // set the server into read-only mode + transactionService.setAllowWrite(false); + } // do we need to perform a full recovery if (fullRecoveryRequired) @@ -160,8 +178,9 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent Transaction lastTxn = null; while(true) { + long lastTxnId = (lastTxn == null) ? -1L : lastTxn.getId().longValue(); List nextTxns = nodeDaoService.getNextTxns( - lastTxn, + lastTxnId, MAX_TRANSACTIONS_PER_ITERATION); // reindex each transaction @@ -256,125 +275,4 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent TransactionUtil.executeInNonPropagatingUserTransaction(transactionService, reindexWork, true); // done } - - private boolean isTxnIdPresentInIndex(StoreRef storeRef, Transaction txn) - { - if (logger.isDebugEnabled()) - { - logger.debug("Checking for transaction in index: \n" + - " store: " + storeRef + "\n" + - " txn: " + txn); - } - - String changeTxnId = txn.getChangeTxnId(); - // count the changes in the transaction - int updateCount = nodeDaoService.getTxnUpdateCountForStore(storeRef, txn.getId()); - int deleteCount = nodeDaoService.getTxnDeleteCountForStore(storeRef, txn.getId()); - if (logger.isDebugEnabled()) - { - logger.debug("Transaction has " + updateCount + " updates and " + deleteCount + " deletes: " + txn); - } - - // do the most update check, which is most common - if (deleteCount == 0 && updateCount == 0) - { - if (logger.isDebugEnabled()) - { - logger.debug("No changes in transaction: " + txn); - } - // there's nothing to check for - return true; - } - else if (updateCount > 0) - { - ResultSet results = null; - try - { - SearchParameters sp = new SearchParameters(); - sp.addStore(storeRef); - // search for it in the index, sorting with youngest first, fetching only 1 - sp.setLanguage(SearchService.LANGUAGE_LUCENE); - sp.setQuery("TX:" + LuceneQueryParser.escape(changeTxnId)); - sp.setLimit(1); - - results = searcher.query(sp); - - if (results.length() > 0) - { - if (logger.isDebugEnabled()) - { - logger.debug("Index has results for txn (OK): " + txn); - } - return true; // there were updates/creates and results for the txn were found - } - else - { - if (logger.isDebugEnabled()) - { - logger.debug("Index has no results for txn (Index out of date): " + txn); - } - return false; - } - } - finally - { - if (results != null) { results.close(); } - } - } - // there have been deletes, so we have to ensure that none of the nodes deleted are present in the index - // get all node refs for the transaction - Long txnId = txn.getId(); - List nodeRefs = nodeDaoService.getTxnChangesForStore(storeRef, txnId); - for (NodeRef nodeRef : nodeRefs) - { - if (logger.isDebugEnabled()) - { - logger.debug("Searching for node in index: \n" + - " node: " + nodeRef + "\n" + - " txn: " + txn); - } - // we know that these are all deletions - ResultSet results = null; - try - { - SearchParameters sp = new SearchParameters(); - sp.addStore(storeRef); - // search for it in the index, sorting with youngest first, fetching only 1 - sp.setLanguage(SearchService.LANGUAGE_LUCENE); - sp.setQuery("ID:" + LuceneQueryParser.escape(nodeRef.toString())); - sp.setLimit(1); - - results = searcher.query(sp); - - if (results.length() == 0) - { - // no results, as expected - if (logger.isDebugEnabled()) - { - logger.debug(" --> Node not found (OK)"); - } - continue; - } - else - { - if (logger.isDebugEnabled()) - { - logger.debug(" --> Node found (Index out of date)"); - } - return false; - } - } - finally - { - if (results != null) { results.close(); } - } - } - - // all tests passed - if (logger.isDebugEnabled()) - { - logger.debug("Index is in synch with transaction: " + txn); - } - return true; - } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTracker.java b/source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTracker.java new file mode 100644 index 0000000000..5f0c49e95a --- /dev/null +++ b/source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTracker.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.node.index; + +import java.util.List; + +import org.alfresco.repo.domain.Transaction; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Component to check and recover the indexes. + * + * @author Derek Hulley + */ +public class IndexRemoteTransactionTracker extends AbstractReindexComponent +{ + private static Log logger = LogFactory.getLog(IndexRemoteTransactionTracker.class); + + private boolean remoteOnly; + private long currentTxnId; + + public IndexRemoteTransactionTracker() + { + remoteOnly = true; + currentTxnId = -1L; + } + + /** + * Set whether or not this component should only track remote transactions. + * By default, it is true, but under certain test conditions, it may + * be desirable to track local transactions too; e.g. during testing of clustering + * when running multiple instances on the same machine. + * + * @param remoteOnly true to reindex only those transactions that were + * committed to the database by a remote server. + */ + public void setRemoteOnly(boolean remoteOnly) + { + this.remoteOnly = remoteOnly; + } + + + + @Override + protected void reindexImpl() + { + if (currentTxnId < 0) + { + // initialize the starting point + Transaction lastTxn = nodeDaoService.getLastTxn(); + if (lastTxn == null) + { + // there is nothing to do + return; + } + long lastTxnId = lastTxn.getId(); + currentTxnId = getLastIndexedTxn(lastTxnId); + } + + if (logger.isDebugEnabled()) + { + logger.debug("Performing index tracking from txn " + currentTxnId); + } + + while (true) + { + // get next transactions to index + List txns = getNextTransactions(currentTxnId); + if (txns.size() == 0) + { + // we've caught up + break; + } + // break out if the VM is shutting down + if (isShuttingDown()) + { + break; + } + // reindex all "foreign" or "local" transactions, one at a time + for (Transaction txn : txns) + { + long txnId = txn.getId(); + reindexTransaction(txnId); + currentTxnId = txnId; + } + } + } + + private static final int MAX_TXN_COUNT = 1000; + private List getNextTransactions(long currentTxnId) + { + List txns = null; + if (remoteOnly) + { + txns = nodeDaoService.getNextRemoteTxns(currentTxnId, MAX_TXN_COUNT); + } + else + { + txns = nodeDaoService.getNextTxns(currentTxnId, MAX_TXN_COUNT); + } + // done + return txns; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTrackerTest.java b/source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTrackerTest.java new file mode 100644 index 0000000000..a33ff0bf40 --- /dev/null +++ b/source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTrackerTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.node.index; + +import junit.framework.TestCase; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.ContentStore; +import org.alfresco.repo.node.db.NodeDaoService; +import org.alfresco.repo.search.Indexer; +import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.transaction.TransactionComponent; +import org.alfresco.repo.transaction.TransactionUtil; +import org.alfresco.repo.transaction.TransactionUtil.TransactionWork; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.springframework.context.ApplicationContext; + +/** + * @see org.alfresco.repo.node.index.IndexRemoteTransactionTracker + * + * @author Derek Hulley + */ +@SuppressWarnings("unused") +public class IndexRemoteTransactionTrackerTest extends TestCase +{ + private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + private AuthenticationComponent authenticationComponent; + private SearchService searchService; + private NodeService nodeService; + private FileFolderService fileFolderService; + private ContentStore contentStore; + private FullTextSearchIndexer ftsIndexer; + private Indexer indexer; + private NodeRef rootNodeRef; + + private IndexRemoteTransactionTracker indexTracker; + + public void setUp() throws Exception + { + ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); + searchService = serviceRegistry.getSearchService(); + nodeService = serviceRegistry.getNodeService(); + fileFolderService = serviceRegistry.getFileFolderService(); + authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponentImpl"); + contentStore = (ContentStore) ctx.getBean("fileContentStore"); + ftsIndexer = (FullTextSearchIndexer) ctx.getBean("LuceneFullTextSearchIndexer"); + + indexer = (Indexer) ctx.getBean("indexerComponent"); + NodeDaoService nodeDaoService = (NodeDaoService) ctx.getBean("nodeDaoService"); + TransactionService transactionService = serviceRegistry.getTransactionService(); + indexTracker = new IndexRemoteTransactionTracker(); + indexTracker.setAuthenticationComponent(authenticationComponent); + indexTracker.setFtsIndexer(ftsIndexer); + indexTracker.setIndexer(indexer); + indexTracker.setNodeDaoService(nodeDaoService); + indexTracker.setNodeService(nodeService); + indexTracker.setSearcher(searchService); + indexTracker.setTransactionComponent((TransactionComponent)transactionService); + + // authenticate + authenticationComponent.setSystemUserAsCurrentUser(); + + // disable indexing + TransactionWork createNodeWork = new TransactionWork() + { + public ChildAssociationRef doWork() throws Exception + { + StoreRef storeRef = new StoreRef("test", getName() + "-" + System.currentTimeMillis()); + NodeRef rootNodeRef = null; + if (!nodeService.exists(storeRef)) + { + nodeService.createStore(storeRef.getProtocol(), storeRef.getIdentifier()); + } + rootNodeRef = nodeService.getRootNode(storeRef); + // create another node + ChildAssociationRef childAssocRef = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.ALFRESCO_URI, "xyz"), + ContentModel.TYPE_FOLDER); + // remove the node from the index + indexer.deleteNode(childAssocRef); + return childAssocRef; + } + }; + ChildAssociationRef childAssocRef = TransactionUtil.executeInUserTransaction(transactionService, createNodeWork); + } + + public void testSetup() throws Exception + { + + } + + public synchronized void testStartup() throws Exception + { + indexTracker.reindex(); + indexTracker.reindex(); + } +} diff --git a/source/java/org/alfresco/repo/rule/BaseRuleTest.java b/source/java/org/alfresco/repo/rule/BaseRuleTest.java index 332f0495dd..d15d1ea7d6 100644 --- a/source/java/org/alfresco/repo/rule/BaseRuleTest.java +++ b/source/java/org/alfresco/repo/rule/BaseRuleTest.java @@ -118,7 +118,8 @@ public class BaseRuleTest extends BaseSpringTest this.transactionService = (TransactionService)this.applicationContext.getBean("transactionComponent"); this.authenticationComponent = (AuthenticationComponent)this.applicationContext.getBean("authenticationComponent"); - authenticationComponent.setSystemUserAsCurrentUser(); + //authenticationComponent.setSystemUserAsCurrentUser(); + authenticationComponent.setCurrentUser("admin"); // Get the rule type this.ruleType = this.ruleService.getRuleType(RULE_TYPE_NAME); diff --git a/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java b/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java index c00ed1e840..0374d8092e 100644 --- a/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java +++ b/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java @@ -149,7 +149,8 @@ public class RuleServiceCoverageTest extends TestCase this.authenticationComponent = (AuthenticationComponent)applicationContext.getBean("authenticationComponent"); //authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); - authenticationComponent.setSystemUserAsCurrentUser(); + //authenticationComponent.setSystemUserAsCurrentUser(); + authenticationComponent.setCurrentUser("admin"); this.testStoreRef = this.nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); this.rootNodeRef = this.nodeService.getRootNode(this.testStoreRef); diff --git a/source/java/org/alfresco/repo/rule/RuleServiceImpl.java b/source/java/org/alfresco/repo/rule/RuleServiceImpl.java index 9bf1eea747..6d0b9420b7 100644 --- a/source/java/org/alfresco/repo/rule/RuleServiceImpl.java +++ b/source/java/org/alfresco/repo/rule/RuleServiceImpl.java @@ -40,6 +40,8 @@ import org.alfresco.service.cmr.rule.Rule; import org.alfresco.service.cmr.rule.RuleService; import org.alfresco.service.cmr.rule.RuleServiceException; import org.alfresco.service.cmr.rule.RuleType; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.util.GUID; @@ -92,6 +94,11 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService */ private DictionaryService dictionaryService; + /** + * The permission service + */ + private PermissionService permissionService; + /** * The action service implementation which we need for some things. */ @@ -179,6 +186,16 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService this.dictionaryService = dictionaryService; } + /** + * Set the permission service + * + * @param permissionService the permission service + */ + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + /** * Set the global rules disabled flag * @@ -572,49 +589,56 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService */ public void saveRule(NodeRef nodeRef, Rule rule) { - disableRules(); - try - { - if (this.nodeService.exists(nodeRef) == false) - { - throw new RuleServiceException("The node does not exist."); - } - - NodeRef ruleNodeRef = rule.getNodeRef(); - if (ruleNodeRef == null) - { - if (this.nodeService.hasAspect(nodeRef, RuleModel.ASPECT_RULES) == false) - { - // Add the actionable aspect - this.nodeService.addAspect(nodeRef, RuleModel.ASPECT_RULES, null); - } - - // Create the action node - ruleNodeRef = this.nodeService.createNode( - getSavedRuleFolderRef(nodeRef), - ContentModel.ASSOC_CONTAINS, - QName.createQName(RuleModel.RULE_MODEL_URI, ASSOC_NAME_RULES_PREFIX + GUID.generate()), - RuleModel.TYPE_RULE).getChildRef(); - - // Set the rule node reference and the owning node reference - rule.setNodeRef(ruleNodeRef); - } - - // Update the properties of the rule - this.nodeService.setProperty(ruleNodeRef, ContentModel.PROP_TITLE, rule.getTitle()); - this.nodeService.setProperty(ruleNodeRef, ContentModel.PROP_DESCRIPTION, rule.getDescription()); - this.nodeService.setProperty(ruleNodeRef, RuleModel.PROP_RULE_TYPE, (Serializable)rule.getRuleTypes()); - this.nodeService.setProperty(ruleNodeRef, RuleModel.PROP_APPLY_TO_CHILDREN, rule.isAppliedToChildren()); - this.nodeService.setProperty(ruleNodeRef, RuleModel.PROP_EXECUTE_ASYNC, rule.getExecuteAsynchronously()); - this.nodeService.setProperty(ruleNodeRef, RuleModel.PROP_DISABLED, rule.getRuleDisabled()); - - // Save the rule's action - saveAction(ruleNodeRef, rule); - } - finally - { - enableRules(); - } + if (this.permissionService.hasPermission(nodeRef, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED) + { + disableRules(); + try + { + if (this.nodeService.exists(nodeRef) == false) + { + throw new RuleServiceException("The node does not exist."); + } + + NodeRef ruleNodeRef = rule.getNodeRef(); + if (ruleNodeRef == null) + { + if (this.nodeService.hasAspect(nodeRef, RuleModel.ASPECT_RULES) == false) + { + // Add the actionable aspect + this.nodeService.addAspect(nodeRef, RuleModel.ASPECT_RULES, null); + } + + // Create the action node + ruleNodeRef = this.nodeService.createNode( + getSavedRuleFolderRef(nodeRef), + ContentModel.ASSOC_CONTAINS, + QName.createQName(RuleModel.RULE_MODEL_URI, ASSOC_NAME_RULES_PREFIX + GUID.generate()), + RuleModel.TYPE_RULE).getChildRef(); + + // Set the rule node reference and the owning node reference + rule.setNodeRef(ruleNodeRef); + } + + // Update the properties of the rule + this.nodeService.setProperty(ruleNodeRef, ContentModel.PROP_TITLE, rule.getTitle()); + this.nodeService.setProperty(ruleNodeRef, ContentModel.PROP_DESCRIPTION, rule.getDescription()); + this.nodeService.setProperty(ruleNodeRef, RuleModel.PROP_RULE_TYPE, (Serializable)rule.getRuleTypes()); + this.nodeService.setProperty(ruleNodeRef, RuleModel.PROP_APPLY_TO_CHILDREN, rule.isAppliedToChildren()); + this.nodeService.setProperty(ruleNodeRef, RuleModel.PROP_EXECUTE_ASYNC, rule.getExecuteAsynchronously()); + this.nodeService.setProperty(ruleNodeRef, RuleModel.PROP_DISABLED, rule.getRuleDisabled()); + + // Save the rule's action + saveAction(ruleNodeRef, rule); + } + finally + { + enableRules(); + } + } + else + { + throw new RuleServiceException("Insufficient permissions to save a rule."); + } } /** @@ -667,22 +691,29 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService */ public void removeRule(NodeRef nodeRef, Rule rule) { - if (this.nodeService.exists(nodeRef) == true && - this.nodeService.hasAspect(nodeRef, RuleModel.ASPECT_RULES) == true) + if (this.permissionService.hasPermission(nodeRef, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED) { - disableRules(nodeRef); - try - { - NodeRef ruleNodeRef = rule.getNodeRef(); - if (ruleNodeRef != null) - { - this.nodeService.removeChild(getSavedRuleFolderRef(nodeRef), ruleNodeRef); - } - } - finally - { - enableRules(nodeRef); - } + if (this.nodeService.exists(nodeRef) == true && + this.nodeService.hasAspect(nodeRef, RuleModel.ASPECT_RULES) == true) + { + disableRules(nodeRef); + try + { + NodeRef ruleNodeRef = rule.getNodeRef(); + if (ruleNodeRef != null) + { + this.nodeService.removeChild(getSavedRuleFolderRef(nodeRef), ruleNodeRef); + } + } + finally + { + enableRules(nodeRef); + } + } + } + else + { + throw new RuleServiceException("Insufficient permissions to remove a rule."); } } @@ -691,20 +722,27 @@ public class RuleServiceImpl implements RuleService, RuntimeRuleService */ public void removeAllRules(NodeRef nodeRef) { - if (this.nodeService.exists(nodeRef) == true && - this.nodeService.hasAspect(nodeRef, RuleModel.ASPECT_RULES) == true) + if (this.permissionService.hasPermission(nodeRef, PermissionService.CHANGE_PERMISSIONS) == AccessStatus.ALLOWED) { - NodeRef folder = getSavedRuleFolderRef(nodeRef); - if (folder != null) - { - List ruleChildAssocs = this.nodeService.getChildAssocs( - folder, - RegexQNamePattern.MATCH_ALL, ASSOC_NAME_RULES_REGEX); - for (ChildAssociationRef ruleChildAssoc : ruleChildAssocs) - { - this.nodeService.removeChild(folder, ruleChildAssoc.getChildRef()); - } - } + if (this.nodeService.exists(nodeRef) == true && + this.nodeService.hasAspect(nodeRef, RuleModel.ASPECT_RULES) == true) + { + NodeRef folder = getSavedRuleFolderRef(nodeRef); + if (folder != null) + { + List ruleChildAssocs = this.nodeService.getChildAssocs( + folder, + RegexQNamePattern.MATCH_ALL, ASSOC_NAME_RULES_REGEX); + for (ChildAssociationRef ruleChildAssoc : ruleChildAssocs) + { + this.nodeService.removeChild(folder, ruleChildAssoc.getChildRef()); + } + } + } + } + else + { + throw new RuleServiceException("Insufficient permissions to remove a rule."); } } diff --git a/source/java/org/alfresco/repo/rule/RuleServiceImplTest.java b/source/java/org/alfresco/repo/rule/RuleServiceImplTest.java index 10f34d4820..cdadf1e724 100644 --- a/source/java/org/alfresco/repo/rule/RuleServiceImplTest.java +++ b/source/java/org/alfresco/repo/rule/RuleServiceImplTest.java @@ -27,6 +27,8 @@ import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; import org.alfresco.repo.action.executer.ImageTransformActionExecuter; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.transform.AbstractContentTransformerTest; +import org.alfresco.repo.transaction.TransactionUtil; +import org.alfresco.repo.transaction.TransactionUtil.TransactionWork; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionCondition; import org.alfresco.service.cmr.repository.ContentWriter; @@ -34,7 +36,10 @@ import org.alfresco.service.cmr.repository.CyclicChildRelationshipException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.rule.Rule; import org.alfresco.service.cmr.rule.RuleType; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.QName; +import org.apache.commons.digester.SetRootRule; /** @@ -44,7 +49,17 @@ import org.alfresco.service.namespace.QName; */ public class RuleServiceImplTest extends BaseRuleTest { - + AuthenticationService authenticationService; + PermissionService permissionService; + + @Override + protected void onSetUpInTransaction() throws Exception + { + super.onSetUpInTransaction(); + this.permissionService = (PermissionService)this.applicationContext.getBean("permissionService"); + this.authenticationService = (AuthenticationService)this.applicationContext.getBean("authenticationService"); + } + /** * Test get rule type */ @@ -296,6 +311,59 @@ public class RuleServiceImplTest extends BaseRuleTest ContentModel.TYPE_CONTAINER).getChildRef(); } + public void testRuleServicePermissionsConsumer() + { + this.authenticationService.createAuthentication("conUser", "password".toCharArray()); + this.permissionService.setPermission(this.nodeRef, "conUser", PermissionService.CONSUMER, true); + this.permissionService.setInheritParentPermissions(this.nodeRef, true); + + this.authenticationService.authenticate("conUser", "password".toCharArray()); + Rule rule = createTestRule(); + try + { + this.ruleService.saveRule(this.nodeRef, rule); + // Fail + fail("Consumers cannot create rules."); + } + catch (Exception exception) + { + // Ok + } + + } + + public void testRuleServicePermissionsEditor() + { + this.authenticationService.createAuthentication("editorUser", "password".toCharArray()); + this.permissionService.setPermission(this.nodeRef, "editorUser", PermissionService.EDITOR, true); + this.permissionService.setInheritParentPermissions(this.nodeRef, true); + + this.authenticationService.authenticate("editorUser", "password".toCharArray()); + Rule rule = createTestRule(); + try + { + this.ruleService.saveRule(this.nodeRef, rule); + // Fail + fail("Editors cannot create rules."); + } + catch (Exception exception) + { + // Ok + } + } + + public void testRuleServicePermissionsCoordinator() + { + this.authenticationService.createAuthentication("coordUser", "password".toCharArray()); + this.permissionService.setPermission(this.nodeRef, "coordUser", PermissionService.COORDINATOR, true); + this.permissionService.setInheritParentPermissions(this.nodeRef, true); + + this.authenticationService.authenticate("admin", "admin".toCharArray()); + Rule rule2 = createTestRule(); + this.ruleService.saveRule(this.nodeRef, rule2); + this.authenticationService.clearCurrentSecurityContext(); + } + /** * Tests the rule inheritance within the store, checking that the cache is reset correctly when * rules are added and removed. diff --git a/source/java/org/alfresco/repo/search/impl/lucene/LuceneSearcherImpl2.java b/source/java/org/alfresco/repo/search/impl/lucene/LuceneSearcherImpl2.java index 7a9b576895..cf3fa13ae6 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/LuceneSearcherImpl2.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/LuceneSearcherImpl2.java @@ -49,6 +49,9 @@ import org.alfresco.service.namespace.NamespacePrefixResolver; import org.alfresco.service.namespace.QName; import org.alfresco.util.ISO9075; import org.alfresco.util.SearchLanguageConversion; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.Term; +import org.apache.lucene.index.TermEnum; import org.apache.lucene.search.Hits; import org.apache.lucene.search.Query; import org.apache.lucene.search.Searcher; @@ -230,7 +233,7 @@ public class LuceneSearcherImpl2 extends LuceneBase2 implements LuceneSearcher2 switch (sd.getSortType()) { case FIELD: - if (searcher.getReader().getFieldNames().contains(sd.getField())) + if (fieldHasTerm(searcher.getReader(), sd.getField())) { fields[index++] = new SortField(sd.getField(), !sd.isAscending()); } @@ -308,6 +311,35 @@ public class LuceneSearcherImpl2 extends LuceneBase2 implements LuceneSearcher2 } } + private static boolean fieldHasTerm(IndexReader indexReader, String field) + { + try + { + TermEnum termEnum = indexReader.terms(new Term(field, "")); + try + { + if (termEnum.next()) + { + Term first = termEnum.term(); + return first.field().equals(field); + } + else + { + return false; + } + } + finally + { + termEnum.close(); + } + } + catch (IOException e) + { + throw new SearcherException("Could not find terms for sort field ", e); + } + + } + public ResultSet query(StoreRef store, String language, String query) { return query(store, language, query, null, null); diff --git a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java index d049131c30..ddde7715ce 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java @@ -834,8 +834,16 @@ public class IndexInfo // luceneIndexer.flushPending(); IndexReader deltaReader = buildAndRegisterDeltaReader(id); - IndexReader reader = new MultiReader(new IndexReader[] { + IndexReader reader = null; + if (deletions == null || deletions.size() == 0) + { + reader = new MultiReader(new IndexReader[] {mainIndexReader, deltaReader }); + } + else + { + reader = new MultiReader(new IndexReader[] { new FilterIndexReaderByNodeRefs2(mainIndexReader, deletions, deleteOnlyNodes), deltaReader }); + } reader = ReferenceCountingReadOnlyIndexReaderFactory.createReader("MainReader"+id, reader); ReferenceCounting refCounting = (ReferenceCounting)reader; refCounting.incrementReferenceCount(); diff --git a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPGroupExportSource.java b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPGroupExportSource.java index be0f3ad747..171dc4f4c0 100644 --- a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPGroupExportSource.java +++ b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPGroupExportSource.java @@ -1,790 +1,790 @@ -/* - * Copyright (C) 2005-2006 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.repo.security.authentication.ldap; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.Writer; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; - -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; -import javax.naming.directory.InitialDirContext; -import javax.naming.directory.SearchControls; -import javax.naming.directory.SearchResult; -import javax.transaction.UserTransaction; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.importer.ExportSource; -import org.alfresco.repo.importer.ExportSourceImporterException; -import org.alfresco.repo.security.authority.AuthorityDAO; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.ApplicationContextHelper; -import org.alfresco.util.EqualsHelper; -import org.alfresco.util.GUID; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.dom4j.io.OutputFormat; -import org.dom4j.io.XMLWriter; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.ApplicationContext; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.AttributesImpl; - -public class LDAPGroupExportSource implements ExportSource, InitializingBean -{ - private static Log s_logger = LogFactory.getLog(LDAPGroupExportSource.class); - - private String groupQuery = "(objectclass=groupOfNames)"; - - private String searchBase; - - private String groupIdAttributeName = "cn"; - - private String userIdAttributeName = "uid"; - - private String groupType = "groupOfNames"; - - private String personType = "inetOrgPerson"; - - private LDAPInitialDirContextFactory ldapInitialContextFactory; - - private NamespaceService namespaceService; - - private String memberAttribute = "member"; - - private boolean errorOnMissingMembers = false; - - private QName viewRef; - - private QName viewId; - - private QName viewAssociations; - - private QName childQName; - - private QName viewValueQName; - - private QName viewIdRef; - - private AuthorityDAO authorityDAO; - - private boolean errorOnMissingGID; - - private boolean errorOnMissingUID; - - public LDAPGroupExportSource() - { - super(); - } - - public void setGroupIdAttributeName(String groupIdAttributeName) - { - this.groupIdAttributeName = groupIdAttributeName; - } - - public void setGroupQuery(String groupQuery) - { - this.groupQuery = groupQuery; - } - - public void setGroupType(String groupType) - { - this.groupType = groupType; - } - - public void setLDAPInitialDirContextFactory(LDAPInitialDirContextFactory ldapInitialDirContextFactory) - { - this.ldapInitialContextFactory = ldapInitialDirContextFactory; - } - - public void setMemberAttribute(String memberAttribute) - { - this.memberAttribute = memberAttribute; - } - - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - public void setPersonType(String personType) - { - this.personType = personType; - } - - public void setSearchBase(String searchBase) - { - this.searchBase = searchBase; - } - - public void setUserIdAttributeName(String userIdAttributeName) - { - this.userIdAttributeName = userIdAttributeName; - } - - public void setErrorOnMissingMembers(boolean errorOnMissingMembers) - { - this.errorOnMissingMembers = errorOnMissingMembers; - } - - public void setErrorOnMissingGID(boolean errorOnMissingGID) - { - this.errorOnMissingGID = errorOnMissingGID; - } - - public void setErrorOnMissingUID(boolean errorOnMissingUID) - { - this.errorOnMissingUID = errorOnMissingUID; - } - - public void setAuthorityDAO(AuthorityDAO authorityDAO) - { - this.authorityDAO = authorityDAO; - } - - public void generateExport(XMLWriter writer) - { - HashSet rootGroups = new HashSet(); - HashMap lookup = new HashMap(); - HashSet secondaryLinks = new HashSet(); - - buildGroupsAndRoots(rootGroups, lookup, secondaryLinks); - - buildXML(rootGroups, lookup, secondaryLinks, writer); - - } - - private void buildXML(HashSet rootGroups, HashMap lookup, - HashSet secondaryLinks, XMLWriter writer) - { - - Collection prefixes = namespaceService.getPrefixes(); - QName childQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "childName", namespaceService); - - try - { - AttributesImpl attrs = new AttributesImpl(); - attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, childQName.getLocalName(), childQName - .toPrefixString(), null, ContentModel.TYPE_PERSON.toPrefixString(namespaceService)); - - writer.startDocument(); - - for (String prefix : prefixes) - { - if (!prefix.equals("xml")) - { - String uri = namespaceService.getNamespaceURI(prefix); - writer.startPrefixMapping(prefix, uri); - } - } - - writer.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view", - NamespaceService.REPOSITORY_VIEW_PREFIX + ":" + "view", new AttributesImpl()); - - // Create group structure - - for (Group group : rootGroups) - { - addRootGroup(lookup, group, writer); - } - - // Create secondary links. - - for (SecondaryLink sl : secondaryLinks) - { - addSecondarylink(lookup, sl, writer); - } - - for (String prefix : prefixes) - { - if (!prefix.equals("xml")) - { - writer.endPrefixMapping(prefix); - } - } - - writer.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view", NamespaceService.REPOSITORY_VIEW_PREFIX - + ":" + "view"); - - writer.endDocument(); - } - catch (SAXException e) - { - throw new ExportSourceImporterException("Failed to create file for import.", e); - } - - } - - private void addSecondarylink(HashMap lookup, SecondaryLink sl, XMLWriter writer) - throws SAXException - { - - String fromId = lookup.get(sl.from).guid; - String toId = lookup.get(sl.to).guid; - - AttributesImpl attrs = new AttributesImpl(); - attrs.addAttribute(viewIdRef.getNamespaceURI(), viewIdRef.getLocalName(), viewIdRef.toPrefixString(), null, - fromId); - - writer.startElement(viewRef.getNamespaceURI(), viewRef.getLocalName(), - viewRef.toPrefixString(namespaceService), attrs); - - writer.startElement(viewAssociations.getNamespaceURI(), viewAssociations.getLocalName(), viewAssociations - .toPrefixString(namespaceService), new AttributesImpl()); - - writer.startElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(), - ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService), new AttributesImpl()); - - AttributesImpl attrsRef = new AttributesImpl(); - attrsRef.addAttribute(viewIdRef.getNamespaceURI(), viewIdRef.getLocalName(), viewIdRef.toPrefixString(), null, - toId); - attrsRef.addAttribute(childQName.getNamespaceURI(), childQName.getLocalName(), childQName.toPrefixString(), - null, QName.createQName(ContentModel.USER_MODEL_URI, sl.to).toPrefixString(namespaceService)); - - writer.startElement(viewRef.getNamespaceURI(), viewRef.getLocalName(), - viewRef.toPrefixString(namespaceService), attrsRef); - - writer.endElement(viewRef.getNamespaceURI(), viewRef.getLocalName(), viewRef.toPrefixString(namespaceService)); - - writer.endElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(), - ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService)); - - writer.endElement(viewAssociations.getNamespaceURI(), viewAssociations.getLocalName(), viewAssociations - .toPrefixString(namespaceService)); - - writer.endElement(viewRef.getNamespaceURI(), viewRef.getLocalName(), viewRef.toPrefixString(namespaceService)); - - } - - private void addRootGroup(HashMap lookup, Group group, XMLWriter writer) throws SAXException - { - QName nodeUUID = QName.createQName("sys:node-uuid", namespaceService); - - AttributesImpl attrs = new AttributesImpl(); - attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, childQName.getLocalName(), childQName - .toPrefixString(), null, QName.createQName(ContentModel.USER_MODEL_URI, group.gid).toPrefixString( - namespaceService)); - attrs.addAttribute(viewId.getNamespaceURI(), viewId.getLocalName(), viewId.toPrefixString(), null, group.guid); - - writer.startElement(ContentModel.TYPE_AUTHORITY_CONTAINER.getNamespaceURI(), - ContentModel.TYPE_AUTHORITY_CONTAINER.getLocalName(), ContentModel.TYPE_AUTHORITY_CONTAINER - .toPrefixString(namespaceService), attrs); - - if ((authorityDAO != null) && authorityDAO.authorityExists(group.gid)) - { - NodeRef authNodeRef = authorityDAO.getAuthorityNodeRefOrNull(group.gid); - if (authNodeRef != null) - { - String uguid = authorityDAO.getAuthorityNodeRefOrNull(group.gid).getId(); - - writer.startElement(nodeUUID.getNamespaceURI(), nodeUUID.getLocalName(), nodeUUID - .toPrefixString(namespaceService), new AttributesImpl()); - - writer.characters(uguid.toCharArray(), 0, uguid.length()); - - writer.endElement(nodeUUID.getNamespaceURI(), nodeUUID.getLocalName(), nodeUUID - .toPrefixString(namespaceService)); - } - } - - writer.startElement(ContentModel.PROP_AUTHORITY_NAME.getNamespaceURI(), ContentModel.PROP_AUTHORITY_NAME - .getLocalName(), ContentModel.PROP_AUTHORITY_NAME.toPrefixString(namespaceService), - new AttributesImpl()); - - writer.characters(group.gid.toCharArray(), 0, group.gid.length()); - - writer.endElement(ContentModel.PROP_AUTHORITY_NAME.getNamespaceURI(), ContentModel.PROP_AUTHORITY_NAME - .getLocalName(), ContentModel.PROP_AUTHORITY_NAME.toPrefixString(namespaceService)); - - if (group.members.size() > 0) - { - writer.startElement(ContentModel.PROP_MEMBERS.getNamespaceURI(), ContentModel.PROP_MEMBERS.getLocalName(), - ContentModel.PROP_MEMBERS.toPrefixString(namespaceService), new AttributesImpl()); - - for (String member : group.members) - { - writer.startElement(viewValueQName.getNamespaceURI(), viewValueQName.getLocalName(), viewValueQName - .toPrefixString(namespaceService), new AttributesImpl()); - - writer.characters(member.toCharArray(), 0, member.length()); - - writer.endElement(viewValueQName.getNamespaceURI(), viewValueQName.getLocalName(), viewValueQName - .toPrefixString(namespaceService)); - } - - writer.endElement(ContentModel.PROP_MEMBERS.getNamespaceURI(), ContentModel.PROP_MEMBERS.getLocalName(), - ContentModel.PROP_MEMBERS.toPrefixString(namespaceService)); - } - - for (Group child : group.children) - { - addgroup(lookup, child, writer); - } - - writer.endElement(ContentModel.TYPE_AUTHORITY_CONTAINER.getNamespaceURI(), - ContentModel.TYPE_AUTHORITY_CONTAINER.getLocalName(), ContentModel.TYPE_AUTHORITY_CONTAINER - .toPrefixString(namespaceService)); - - } - - private void addgroup(HashMap lookup, Group group, XMLWriter writer) throws SAXException - { - AttributesImpl attrs = new AttributesImpl(); - - writer.startElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(), - ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService), attrs); - - addRootGroup(lookup, group, writer); - - writer.endElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(), - ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService)); - } - - private void buildGroupsAndRoots(HashSet rootGroups, HashMap lookup, - HashSet secondaryLinks) - { - InitialDirContext ctx = null; - try - { - ctx = ldapInitialContextFactory.getDefaultIntialDirContext(); - - SearchControls userSearchCtls = new SearchControls(); - userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); - - NamingEnumeration searchResults = ctx.search(searchBase, groupQuery, userSearchCtls); - while (searchResults.hasMoreElements()) - { - SearchResult result = (SearchResult) searchResults.next(); - Attributes attributes = result.getAttributes(); - Attribute gidAttribute = attributes.get(groupIdAttributeName); - if (gidAttribute == null) - { - if (errorOnMissingGID) - { - throw new ExportSourceImporterException( - "Group returned by group search does not have mandatory group id attribute " - + attributes); - } - else - { - s_logger.warn("Missing GID on " + attributes); - continue; - } - } - String gid = (String) gidAttribute.get(0); - - Group group = lookup.get(gid); - if (group == null) - { - group = new Group(gid); - lookup.put(group.gid, group); - rootGroups.add(group); - } - Attribute memAttribute = attributes.get(memberAttribute); - // check for null - if (memAttribute != null) - { - for (int i = 0; i < memAttribute.size(); i++) - { - String attribute = (String) memAttribute.get(i); - if (attribute != null) - { - group.distinguishedNames.add(attribute); - } - } - } - } - - if (s_logger.isDebugEnabled()) - { - s_logger.debug("Found " + lookup.size()); - } - - for (Group group : lookup.values()) - { - if (s_logger.isDebugEnabled()) - { - s_logger.debug("Linking " + group.gid); - } - for (String dn : group.distinguishedNames) - { - if (s_logger.isDebugEnabled()) - { - s_logger.debug("... " + dn); - } - String id; - Boolean isGroup = null; - - SearchControls memberSearchCtls = new SearchControls(); - memberSearchCtls.setSearchScope(SearchControls.OBJECT_SCOPE); - NamingEnumeration memberSearchResults; - try - { - memberSearchResults = ctx.search(dn, "(objectClass=*)", memberSearchCtls); - } - catch (NamingException e) - { - if (errorOnMissingMembers) - { - throw e; - } - s_logger.warn("Failed to resolve distinguished name: " + dn); - continue; - } - while (memberSearchResults.hasMoreElements()) - { - id = null; - - SearchResult result; - try - { - result = (SearchResult) memberSearchResults.next(); - } - catch (NamingException e) - { - if (errorOnMissingMembers) - { - throw e; - } - s_logger.warn("Failed to resolve distinguished name: " + dn); - continue; - } - Attributes attributes = result.getAttributes(); - Attribute objectclass = attributes.get("objectclass"); - if (objectclass == null) - { - if (errorOnMissingMembers) - { - throw new ExportSourceImporterException("Failed to find attribute objectclass for DN " - + dn); - } - else - { - continue; - } - } - for (int i = 0; i < objectclass.size(); i++) - { - String testType; - try - { - testType = (String) objectclass.get(i); - } - catch (NamingException e) - { - if (errorOnMissingMembers) - { - throw e; - } - s_logger.warn("Failed to resolve object class attribute for distinguished name: " + dn); - continue; - } - if (testType.equals(groupType)) - { - isGroup = true; - try - { - Attribute groupIdAttribute = attributes.get(groupIdAttributeName); - if (groupIdAttribute == null) - { - if (errorOnMissingGID) - { - throw new ExportSourceImporterException( - "Group missing group id attribute DN =" - + dn + " att = " + groupIdAttributeName); - } - else - { - s_logger.warn("Group missing group id attribute DN =" - + dn + " att = " + groupIdAttributeName); - continue; - } - } - id = (String) groupIdAttribute.get(0); - } - catch (NamingException e) - { - if (errorOnMissingMembers) - { - throw e; - } - s_logger.warn("Failed to resolve group identifier " - + groupIdAttributeName + " for distinguished name: " + dn); - id = "Unknown sub group"; - } - break; - } - else if (testType.equals(personType)) - { - isGroup = false; - try - { - Attribute userIdAttribute = attributes.get(userIdAttributeName); - if (userIdAttribute == null) - { - if (errorOnMissingUID) - { - throw new ExportSourceImporterException( - "User missing user id attribute DN =" - + dn + " att = " + userIdAttributeName); - } - else - { - s_logger.warn("User missing user id attribute DN =" - + dn + " att = " + userIdAttributeName); - continue; - } - } - id = (String) userIdAttribute.get(0); - } - catch (NamingException e) - { - if (errorOnMissingMembers) - { - throw e; - } - s_logger.warn("Failed to resolve group identifier " - + userIdAttributeName + " for distinguished name: " + dn); - id = "Unknown member"; - } - break; - } - } - - if (id != null) - { - if (isGroup == null) - { - if (errorOnMissingMembers) - { - throw new ExportSourceImporterException("Type not recognised for DN" + dn); - } - else - { - continue; - } - } - else if (isGroup) - { - if (s_logger.isDebugEnabled()) - { - s_logger.debug("... is sub group"); - } - Group child = lookup.get("GROUP_" + id); - if (child == null) - { - if (errorOnMissingMembers) - { - throw new ExportSourceImporterException("Failed to find child group " + id); - } - else - { - continue; - } - } - if (rootGroups.contains(child)) - { - if (s_logger.isDebugEnabled()) - { - s_logger.debug("... Primary created from " - + group.gid + " to " + child.gid); - } - group.children.add(child); - rootGroups.remove(child); - } - else - { - if (s_logger.isDebugEnabled()) - { - s_logger.debug("... Secondary created from " - + group.gid + " to " + child.gid); - } - secondaryLinks.add(new SecondaryLink(group.gid, child.gid)); - } - } - else - { - if (s_logger.isDebugEnabled()) - { - s_logger.debug("... is member"); - } - group.members.add(id); - } - } - } - } - } - if (s_logger.isDebugEnabled()) - { - s_logger.debug("Top " + rootGroups.size()); - s_logger.debug("Secondary " + secondaryLinks.size()); - } - } - catch (NamingException e) - { - throw new ExportSourceImporterException("Failed to import people.", e); - } - finally - { - if (ctx != null) - { - try - { - ctx.close(); - } - catch (NamingException e) - { - throw new ExportSourceImporterException("Failed to import people.", e); - } - } - } - } - - private static class Group - { - String gid; - - String guid = GUID.generate(); - - HashSet children = new HashSet(); - - HashSet members = new HashSet(); - - HashSet distinguishedNames = new HashSet(); - - private Group(String gid) - { - this.gid = "GROUP_" + gid; - } - - @Override - public boolean equals(Object o) - { - if (this == o) - { - return true; - } - if (!(o instanceof Group)) - { - return false; - } - Group g = (Group) o; - return this.gid.equals(g.gid); - } - - @Override - public int hashCode() - { - return gid.hashCode(); - } - } - - private static class SecondaryLink - { - String from; - - String to; - - private SecondaryLink(String from, String to) - { - this.from = from; - this.to = to; - } - - @Override - public boolean equals(Object o) - { - if (this == o) - { - return true; - } - if (!(o instanceof Group)) - { - return false; - } - SecondaryLink l = (SecondaryLink) o; - return EqualsHelper.nullSafeEquals(this.from, l.from) && EqualsHelper.nullSafeEquals(this.to, l.to); - } - - @Override - public int hashCode() - { - int hashCode = 0; - if (from != null) - { - hashCode = hashCode * 37 + from.hashCode(); - } - if (to != null) - { - hashCode = hashCode * 37 + to.hashCode(); - } - return hashCode; - } - } - - public static void main(String[] args) throws Exception - { - ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); - ExportSource source = (ExportSource) ctx.getBean("ldapGroupExportSource"); - - TransactionService txs = (TransactionService) ctx.getBean("transactionComponent"); - UserTransaction tx = txs.getUserTransaction(); - tx.begin(); - - File file = new File(args[0]); - Writer writer = new BufferedWriter(new FileWriter(file)); - XMLWriter xmlWriter = createXMLExporter(writer); - source.generateExport(xmlWriter); - xmlWriter.close(); - - tx.commit(); - } - - private static XMLWriter createXMLExporter(Writer writer) - { - // Define output format - OutputFormat format = OutputFormat.createPrettyPrint(); - format.setNewLineAfterDeclaration(false); - format.setIndentSize(3); - format.setEncoding("UTF-8"); - - // Construct an XML Exporter - - XMLWriter xmlWriter = new XMLWriter(writer, format); - return xmlWriter; - } - - public void afterPropertiesSet() throws Exception - { - viewRef = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "reference", namespaceService); - viewId = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "id", namespaceService); - viewIdRef = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "idref", namespaceService); - viewAssociations = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "associations", namespaceService); - childQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "childName", namespaceService); - viewValueQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "value", namespaceService); - - } -} +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.security.authentication.ldap; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.Writer; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.InitialDirContext; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; +import javax.transaction.UserTransaction; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.importer.ExportSource; +import org.alfresco.repo.importer.ExportSourceImporterException; +import org.alfresco.repo.security.authority.AuthorityDAO; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.EqualsHelper; +import org.alfresco.util.GUID; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.dom4j.io.OutputFormat; +import org.dom4j.io.XMLWriter; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +public class LDAPGroupExportSource implements ExportSource, InitializingBean +{ + private static Log s_logger = LogFactory.getLog(LDAPGroupExportSource.class); + + private String groupQuery = "(objectclass=groupOfNames)"; + + private String searchBase; + + private String groupIdAttributeName = "cn"; + + private String userIdAttributeName = "uid"; + + private String groupType = "groupOfNames"; + + private String personType = "inetOrgPerson"; + + private LDAPInitialDirContextFactory ldapInitialContextFactory; + + private NamespaceService namespaceService; + + private String memberAttribute = "member"; + + private boolean errorOnMissingMembers = false; + + private QName viewRef; + + private QName viewId; + + private QName viewAssociations; + + private QName childQName; + + private QName viewValueQName; + + private QName viewIdRef; + + private AuthorityDAO authorityDAO; + + private boolean errorOnMissingGID; + + private boolean errorOnMissingUID; + + public LDAPGroupExportSource() + { + super(); + } + + public void setGroupIdAttributeName(String groupIdAttributeName) + { + this.groupIdAttributeName = groupIdAttributeName; + } + + public void setGroupQuery(String groupQuery) + { + this.groupQuery = groupQuery; + } + + public void setGroupType(String groupType) + { + this.groupType = groupType; + } + + public void setLDAPInitialDirContextFactory(LDAPInitialDirContextFactory ldapInitialDirContextFactory) + { + this.ldapInitialContextFactory = ldapInitialDirContextFactory; + } + + public void setMemberAttribute(String memberAttribute) + { + this.memberAttribute = memberAttribute; + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + public void setPersonType(String personType) + { + this.personType = personType; + } + + public void setSearchBase(String searchBase) + { + this.searchBase = searchBase; + } + + public void setUserIdAttributeName(String userIdAttributeName) + { + this.userIdAttributeName = userIdAttributeName; + } + + public void setErrorOnMissingMembers(boolean errorOnMissingMembers) + { + this.errorOnMissingMembers = errorOnMissingMembers; + } + + public void setErrorOnMissingGID(boolean errorOnMissingGID) + { + this.errorOnMissingGID = errorOnMissingGID; + } + + public void setErrorOnMissingUID(boolean errorOnMissingUID) + { + this.errorOnMissingUID = errorOnMissingUID; + } + + public void setAuthorityDAO(AuthorityDAO authorityDAO) + { + this.authorityDAO = authorityDAO; + } + + public void generateExport(XMLWriter writer) + { + HashSet rootGroups = new HashSet(); + HashMap lookup = new HashMap(); + HashSet secondaryLinks = new HashSet(); + + buildGroupsAndRoots(rootGroups, lookup, secondaryLinks); + + buildXML(rootGroups, lookup, secondaryLinks, writer); + + } + + private void buildXML(HashSet rootGroups, HashMap lookup, + HashSet secondaryLinks, XMLWriter writer) + { + + Collection prefixes = namespaceService.getPrefixes(); + QName childQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "childName", namespaceService); + + try + { + AttributesImpl attrs = new AttributesImpl(); + attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, childQName.getLocalName(), childQName + .toPrefixString(), null, ContentModel.TYPE_PERSON.toPrefixString(namespaceService)); + + writer.startDocument(); + + for (String prefix : prefixes) + { + if (!prefix.equals("xml")) + { + String uri = namespaceService.getNamespaceURI(prefix); + writer.startPrefixMapping(prefix, uri); + } + } + + writer.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view", + NamespaceService.REPOSITORY_VIEW_PREFIX + ":" + "view", new AttributesImpl()); + + // Create group structure + + for (Group group : rootGroups) + { + addRootGroup(lookup, group, writer); + } + + // Create secondary links. + + for (SecondaryLink sl : secondaryLinks) + { + addSecondarylink(lookup, sl, writer); + } + + for (String prefix : prefixes) + { + if (!prefix.equals("xml")) + { + writer.endPrefixMapping(prefix); + } + } + + writer.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view", NamespaceService.REPOSITORY_VIEW_PREFIX + + ":" + "view"); + + writer.endDocument(); + } + catch (SAXException e) + { + throw new ExportSourceImporterException("Failed to create file for import.", e); + } + + } + + private void addSecondarylink(HashMap lookup, SecondaryLink sl, XMLWriter writer) + throws SAXException + { + + String fromId = lookup.get(sl.from).guid; + String toId = lookup.get(sl.to).guid; + + AttributesImpl attrs = new AttributesImpl(); + attrs.addAttribute(viewIdRef.getNamespaceURI(), viewIdRef.getLocalName(), viewIdRef.toPrefixString(), null, + fromId); + + writer.startElement(viewRef.getNamespaceURI(), viewRef.getLocalName(), + viewRef.toPrefixString(namespaceService), attrs); + + writer.startElement(viewAssociations.getNamespaceURI(), viewAssociations.getLocalName(), viewAssociations + .toPrefixString(namespaceService), new AttributesImpl()); + + writer.startElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(), + ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService), new AttributesImpl()); + + AttributesImpl attrsRef = new AttributesImpl(); + attrsRef.addAttribute(viewIdRef.getNamespaceURI(), viewIdRef.getLocalName(), viewIdRef.toPrefixString(), null, + toId); + attrsRef.addAttribute(childQName.getNamespaceURI(), childQName.getLocalName(), childQName.toPrefixString(), + null, QName.createQName(ContentModel.USER_MODEL_URI, sl.to).toPrefixString(namespaceService)); + + writer.startElement(viewRef.getNamespaceURI(), viewRef.getLocalName(), + viewRef.toPrefixString(namespaceService), attrsRef); + + writer.endElement(viewRef.getNamespaceURI(), viewRef.getLocalName(), viewRef.toPrefixString(namespaceService)); + + writer.endElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(), + ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService)); + + writer.endElement(viewAssociations.getNamespaceURI(), viewAssociations.getLocalName(), viewAssociations + .toPrefixString(namespaceService)); + + writer.endElement(viewRef.getNamespaceURI(), viewRef.getLocalName(), viewRef.toPrefixString(namespaceService)); + + } + + private void addRootGroup(HashMap lookup, Group group, XMLWriter writer) throws SAXException + { + QName nodeUUID = QName.createQName("sys:node-uuid", namespaceService); + + AttributesImpl attrs = new AttributesImpl(); + attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, childQName.getLocalName(), childQName + .toPrefixString(), null, QName.createQName(ContentModel.USER_MODEL_URI, group.gid).toPrefixString( + namespaceService)); + attrs.addAttribute(viewId.getNamespaceURI(), viewId.getLocalName(), viewId.toPrefixString(), null, group.guid); + + writer.startElement(ContentModel.TYPE_AUTHORITY_CONTAINER.getNamespaceURI(), + ContentModel.TYPE_AUTHORITY_CONTAINER.getLocalName(), ContentModel.TYPE_AUTHORITY_CONTAINER + .toPrefixString(namespaceService), attrs); + + if ((authorityDAO != null) && authorityDAO.authorityExists(group.gid)) + { + NodeRef authNodeRef = authorityDAO.getAuthorityNodeRefOrNull(group.gid); + if (authNodeRef != null) + { + String uguid = authorityDAO.getAuthorityNodeRefOrNull(group.gid).getId(); + + writer.startElement(nodeUUID.getNamespaceURI(), nodeUUID.getLocalName(), nodeUUID + .toPrefixString(namespaceService), new AttributesImpl()); + + writer.characters(uguid.toCharArray(), 0, uguid.length()); + + writer.endElement(nodeUUID.getNamespaceURI(), nodeUUID.getLocalName(), nodeUUID + .toPrefixString(namespaceService)); + } + } + + writer.startElement(ContentModel.PROP_AUTHORITY_NAME.getNamespaceURI(), ContentModel.PROP_AUTHORITY_NAME + .getLocalName(), ContentModel.PROP_AUTHORITY_NAME.toPrefixString(namespaceService), + new AttributesImpl()); + + writer.characters(group.gid.toCharArray(), 0, group.gid.length()); + + writer.endElement(ContentModel.PROP_AUTHORITY_NAME.getNamespaceURI(), ContentModel.PROP_AUTHORITY_NAME + .getLocalName(), ContentModel.PROP_AUTHORITY_NAME.toPrefixString(namespaceService)); + + if (group.members.size() > 0) + { + writer.startElement(ContentModel.PROP_MEMBERS.getNamespaceURI(), ContentModel.PROP_MEMBERS.getLocalName(), + ContentModel.PROP_MEMBERS.toPrefixString(namespaceService), new AttributesImpl()); + + for (String member : group.members) + { + writer.startElement(viewValueQName.getNamespaceURI(), viewValueQName.getLocalName(), viewValueQName + .toPrefixString(namespaceService), new AttributesImpl()); + + writer.characters(member.toCharArray(), 0, member.length()); + + writer.endElement(viewValueQName.getNamespaceURI(), viewValueQName.getLocalName(), viewValueQName + .toPrefixString(namespaceService)); + } + + writer.endElement(ContentModel.PROP_MEMBERS.getNamespaceURI(), ContentModel.PROP_MEMBERS.getLocalName(), + ContentModel.PROP_MEMBERS.toPrefixString(namespaceService)); + } + + for (Group child : group.children) + { + addgroup(lookup, child, writer); + } + + writer.endElement(ContentModel.TYPE_AUTHORITY_CONTAINER.getNamespaceURI(), + ContentModel.TYPE_AUTHORITY_CONTAINER.getLocalName(), ContentModel.TYPE_AUTHORITY_CONTAINER + .toPrefixString(namespaceService)); + + } + + private void addgroup(HashMap lookup, Group group, XMLWriter writer) throws SAXException + { + AttributesImpl attrs = new AttributesImpl(); + + writer.startElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(), + ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService), attrs); + + addRootGroup(lookup, group, writer); + + writer.endElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(), + ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService)); + } + + private void buildGroupsAndRoots(HashSet rootGroups, HashMap lookup, + HashSet secondaryLinks) + { + InitialDirContext ctx = null; + try + { + ctx = ldapInitialContextFactory.getDefaultIntialDirContext(); + + SearchControls userSearchCtls = new SearchControls(); + userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); + + NamingEnumeration searchResults = ctx.search(searchBase, groupQuery, userSearchCtls); + while (searchResults.hasMoreElements()) + { + SearchResult result = (SearchResult) searchResults.next(); + Attributes attributes = result.getAttributes(); + Attribute gidAttribute = attributes.get(groupIdAttributeName); + if (gidAttribute == null) + { + if (errorOnMissingGID) + { + throw new ExportSourceImporterException( + "Group returned by group search does not have mandatory group id attribute " + + attributes); + } + else + { + s_logger.warn("Missing GID on " + attributes); + continue; + } + } + String gid = (String) gidAttribute.get(0); + + Group group = lookup.get(gid); + if (group == null) + { + group = new Group(gid); + lookup.put(group.gid, group); + rootGroups.add(group); + } + Attribute memAttribute = attributes.get(memberAttribute); + // check for null + if (memAttribute != null) + { + for (int i = 0; i < memAttribute.size(); i++) + { + String attribute = (String) memAttribute.get(i); + if (attribute != null) + { + group.distinguishedNames.add(attribute); + } + } + } + } + + if (s_logger.isDebugEnabled()) + { + s_logger.debug("Found " + lookup.size()); + } + + for (Group group : lookup.values()) + { + if (s_logger.isDebugEnabled()) + { + s_logger.debug("Linking " + group.gid); + } + for (String dn : group.distinguishedNames) + { + if (s_logger.isDebugEnabled()) + { + s_logger.debug("... " + dn); + } + String id; + Boolean isGroup = null; + + SearchControls memberSearchCtls = new SearchControls(); + memberSearchCtls.setSearchScope(SearchControls.OBJECT_SCOPE); + NamingEnumeration memberSearchResults; + try + { + memberSearchResults = ctx.search(dn, "(objectClass=*)", memberSearchCtls); + } + catch (NamingException e) + { + if (errorOnMissingMembers) + { + throw e; + } + s_logger.warn("Failed to resolve distinguished name: " + dn); + continue; + } + while (memberSearchResults.hasMoreElements()) + { + id = null; + + SearchResult result; + try + { + result = (SearchResult) memberSearchResults.next(); + } + catch (NamingException e) + { + if (errorOnMissingMembers) + { + throw e; + } + s_logger.warn("Failed to resolve distinguished name: " + dn); + continue; + } + Attributes attributes = result.getAttributes(); + Attribute objectclass = attributes.get("objectclass"); + if (objectclass == null) + { + if (errorOnMissingMembers) + { + throw new ExportSourceImporterException("Failed to find attribute objectclass for DN " + + dn); + } + else + { + continue; + } + } + for (int i = 0; i < objectclass.size(); i++) + { + String testType; + try + { + testType = (String) objectclass.get(i); + } + catch (NamingException e) + { + if (errorOnMissingMembers) + { + throw e; + } + s_logger.warn("Failed to resolve object class attribute for distinguished name: " + dn); + continue; + } + if (testType.equals(groupType)) + { + isGroup = true; + try + { + Attribute groupIdAttribute = attributes.get(groupIdAttributeName); + if (groupIdAttribute == null) + { + if (errorOnMissingGID) + { + throw new ExportSourceImporterException( + "Group missing group id attribute DN =" + + dn + " att = " + groupIdAttributeName); + } + else + { + s_logger.warn("Group missing group id attribute DN =" + + dn + " att = " + groupIdAttributeName); + continue; + } + } + id = (String) groupIdAttribute.get(0); + } + catch (NamingException e) + { + if (errorOnMissingMembers) + { + throw e; + } + s_logger.warn("Failed to resolve group identifier " + + groupIdAttributeName + " for distinguished name: " + dn); + id = "Unknown sub group"; + } + break; + } + else if (testType.equals(personType)) + { + isGroup = false; + try + { + Attribute userIdAttribute = attributes.get(userIdAttributeName); + if (userIdAttribute == null) + { + if (errorOnMissingUID) + { + throw new ExportSourceImporterException( + "User missing user id attribute DN =" + + dn + " att = " + userIdAttributeName); + } + else + { + s_logger.warn("User missing user id attribute DN =" + + dn + " att = " + userIdAttributeName); + continue; + } + } + id = (String) userIdAttribute.get(0); + } + catch (NamingException e) + { + if (errorOnMissingMembers) + { + throw e; + } + s_logger.warn("Failed to resolve group identifier " + + userIdAttributeName + " for distinguished name: " + dn); + id = "Unknown member"; + } + break; + } + } + + if (id != null) + { + if (isGroup == null) + { + if (errorOnMissingMembers) + { + throw new ExportSourceImporterException("Type not recognised for DN" + dn); + } + else + { + continue; + } + } + else if (isGroup) + { + if (s_logger.isDebugEnabled()) + { + s_logger.debug("... is sub group"); + } + Group child = lookup.get("GROUP_" + id); + if (child == null) + { + if (errorOnMissingMembers) + { + throw new ExportSourceImporterException("Failed to find child group " + id); + } + else + { + continue; + } + } + if (rootGroups.contains(child)) + { + if (s_logger.isDebugEnabled()) + { + s_logger.debug("... Primary created from " + + group.gid + " to " + child.gid); + } + group.children.add(child); + rootGroups.remove(child); + } + else + { + if (s_logger.isDebugEnabled()) + { + s_logger.debug("... Secondary created from " + + group.gid + " to " + child.gid); + } + secondaryLinks.add(new SecondaryLink(group.gid, child.gid)); + } + } + else + { + if (s_logger.isDebugEnabled()) + { + s_logger.debug("... is member"); + } + group.members.add(id); + } + } + } + } + } + if (s_logger.isDebugEnabled()) + { + s_logger.debug("Top " + rootGroups.size()); + s_logger.debug("Secondary " + secondaryLinks.size()); + } + } + catch (NamingException e) + { + throw new ExportSourceImporterException("Failed to import people.", e); + } + finally + { + if (ctx != null) + { + try + { + ctx.close(); + } + catch (NamingException e) + { + throw new ExportSourceImporterException("Failed to import people.", e); + } + } + } + } + + private static class Group + { + String gid; + + String guid = GUID.generate(); + + HashSet children = new HashSet(); + + HashSet members = new HashSet(); + + HashSet distinguishedNames = new HashSet(); + + private Group(String gid) + { + this.gid = "GROUP_" + gid; + } + + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (!(o instanceof Group)) + { + return false; + } + Group g = (Group) o; + return this.gid.equals(g.gid); + } + + @Override + public int hashCode() + { + return gid.hashCode(); + } + } + + private static class SecondaryLink + { + String from; + + String to; + + private SecondaryLink(String from, String to) + { + this.from = from; + this.to = to; + } + + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (!(o instanceof Group)) + { + return false; + } + SecondaryLink l = (SecondaryLink) o; + return EqualsHelper.nullSafeEquals(this.from, l.from) && EqualsHelper.nullSafeEquals(this.to, l.to); + } + + @Override + public int hashCode() + { + int hashCode = 0; + if (from != null) + { + hashCode = hashCode * 37 + from.hashCode(); + } + if (to != null) + { + hashCode = hashCode * 37 + to.hashCode(); + } + return hashCode; + } + } + + public static void main(String[] args) throws Exception + { + ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + ExportSource source = (ExportSource) ctx.getBean("ldapGroupExportSource"); + + TransactionService txs = (TransactionService) ctx.getBean("transactionComponent"); + UserTransaction tx = txs.getUserTransaction(); + tx.begin(); + + File file = new File(args[0]); + Writer writer = new BufferedWriter(new FileWriter(file)); + XMLWriter xmlWriter = createXMLExporter(writer); + source.generateExport(xmlWriter); + xmlWriter.close(); + + tx.commit(); + } + + private static XMLWriter createXMLExporter(Writer writer) + { + // Define output format + OutputFormat format = OutputFormat.createPrettyPrint(); + format.setNewLineAfterDeclaration(false); + format.setIndentSize(3); + format.setEncoding("UTF-8"); + + // Construct an XML Exporter + + XMLWriter xmlWriter = new XMLWriter(writer, format); + return xmlWriter; + } + + public void afterPropertiesSet() throws Exception + { + viewRef = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "reference", namespaceService); + viewId = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "id", namespaceService); + viewIdRef = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "idref", namespaceService); + viewAssociations = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "associations", namespaceService); + childQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "childName", namespaceService); + viewValueQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "value", namespaceService); + + } +} diff --git a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPPersonExportSource.java b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPPersonExportSource.java index c9b929183f..1b05e495b3 100644 --- a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPPersonExportSource.java +++ b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPPersonExportSource.java @@ -1,342 +1,342 @@ -/* - * Copyright (C) 2005-2006 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.repo.security.authentication.ldap; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.Writer; -import java.util.Collection; -import java.util.Map; - -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; -import javax.naming.directory.InitialDirContext; -import javax.naming.directory.SearchControls; -import javax.naming.directory.SearchResult; -import javax.transaction.UserTransaction; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.importer.ExportSource; -import org.alfresco.repo.importer.ExportSourceImporterException; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.ApplicationContextHelper; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.dom4j.io.OutputFormat; -import org.dom4j.io.XMLWriter; -import org.springframework.context.ApplicationContext; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.AttributesImpl; - -public class LDAPPersonExportSource implements ExportSource -{ - private static Log s_logger = LogFactory.getLog(LDAPPersonExportSource.class); - - private String personQuery = "(objectclass=inetOrgPerson)"; - - private String searchBase; - - private String userIdAttributeName; - - private LDAPInitialDirContextFactory ldapInitialContextFactory; - - private PersonService personService; - - private Map attributeMapping; - - private NamespaceService namespaceService; - - private Map attributeDefaults; - - private boolean errorOnMissingUID; - - public LDAPPersonExportSource() - { - super(); - } - - public void setPersonQuery(String personQuery) - { - this.personQuery = personQuery; - } - - public void setSearchBase(String searchBase) - { - this.searchBase = searchBase; - } - - public void setUserIdAttributeName(String userIdAttributeName) - { - this.userIdAttributeName = userIdAttributeName; - } - - public void setLDAPInitialDirContextFactory(LDAPInitialDirContextFactory ldapInitialDirContextFactory) - { - this.ldapInitialContextFactory = ldapInitialDirContextFactory; - } - - public void setPersonService(PersonService personService) - { - this.personService = personService; - } - - public void setAttributeDefaults(Map attributeDefaults) - { - this.attributeDefaults = attributeDefaults; - } - - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - public void setAttributeMapping(Map attributeMapping) - { - this.attributeMapping = attributeMapping; - } - - public void setErrorOnMissingUID(boolean errorOnMissingUID) - { - this.errorOnMissingUID = errorOnMissingUID; - } - - public void generateExport(XMLWriter writer) - { - QName nodeUUID = QName.createQName("sys:node-uuid", namespaceService); - - Collection prefixes = namespaceService.getPrefixes(); - QName childQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "childName", namespaceService); - - try - { - AttributesImpl attrs = new AttributesImpl(); - attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, childQName.getLocalName(), childQName - .toPrefixString(), null, ContentModel.TYPE_PERSON.toPrefixString(namespaceService)); - - writer.startDocument(); - - for (String prefix : prefixes) - { - if (!prefix.equals("xml")) - { - String uri = namespaceService.getNamespaceURI(prefix); - writer.startPrefixMapping(prefix, uri); - } - } - - writer.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view", - NamespaceService.REPOSITORY_VIEW_PREFIX + ":" + "view", new AttributesImpl()); - - InitialDirContext ctx = null; - try - { - ctx = ldapInitialContextFactory.getDefaultIntialDirContext(); - - // Authentication has been successful. - // Set the current user, they are now authenticated. - - SearchControls userSearchCtls = new SearchControls(); - userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); - - userSearchCtls.setCountLimit(Integer.MAX_VALUE); - - NamingEnumeration searchResults = ctx.search(searchBase, personQuery, userSearchCtls); - while (searchResults.hasMoreElements()) - { - SearchResult result = (SearchResult) searchResults.next(); - Attributes attributes = result.getAttributes(); - Attribute uidAttribute = attributes.get(userIdAttributeName); - if (uidAttribute == null) - { - if(errorOnMissingUID) - { - throw new ExportSourceImporterException( - "User returned by user search does not have mandatory user id attribute " + attributes); - } - else - { - s_logger.warn("User returned by user search does not have mandatory user id attribute " + attributes); - continue; - } - } - String uid = (String) uidAttribute.get(0); - - if (s_logger.isDebugEnabled()) - { - s_logger.debug("Adding user for " + uid); - } - - - writer.startElement(ContentModel.TYPE_PERSON.getNamespaceURI(), ContentModel.TYPE_PERSON - .getLocalName(), ContentModel.TYPE_PERSON.toPrefixString(namespaceService), attrs); - - // permissions - - // owner - - writer.startElement(ContentModel.ASPECT_OWNABLE.getNamespaceURI(), ContentModel.ASPECT_OWNABLE - .getLocalName(), ContentModel.ASPECT_OWNABLE.toPrefixString(namespaceService), - new AttributesImpl()); - - writer.endElement(ContentModel.ASPECT_OWNABLE.getNamespaceURI(), ContentModel.ASPECT_OWNABLE - .getLocalName(), ContentModel.ASPECT_OWNABLE.toPrefixString(namespaceService)); - - writer.startElement(ContentModel.PROP_OWNER.getNamespaceURI(), ContentModel.PROP_OWNER - .getLocalName(), ContentModel.PROP_OWNER.toPrefixString(namespaceService), - new AttributesImpl()); - - writer.characters(uid.toCharArray(), 0, uid.length()); - - writer.endElement(ContentModel.PROP_OWNER.getNamespaceURI(), - ContentModel.PROP_OWNER.getLocalName(), ContentModel.PROP_OWNER - .toPrefixString(namespaceService)); - - for (String key : attributeMapping.keySet()) - { - QName keyQName = QName.createQName(key, namespaceService); - - writer.startElement(keyQName.getNamespaceURI(), keyQName.getLocalName(), keyQName - .toPrefixString(namespaceService), new AttributesImpl()); - - // cater for null - String attributeName = attributeMapping.get(key); - if (attributeName != null) - { - Attribute attribute = attributes.get(attributeName); - if (attribute != null) - { - String value = (String) attribute.get(0); - if (value != null) - { - writer.characters(value.toCharArray(), 0, value.length()); - } - } - else - { - String defaultValue = attributeDefaults.get(key); - if(defaultValue != null) - { - writer.characters(defaultValue.toCharArray(), 0, defaultValue.length()); - } - } - } - else - { - String defaultValue = attributeDefaults.get(key); - if(defaultValue != null) - { - writer.characters(defaultValue.toCharArray(), 0, defaultValue.length()); - } - } - - writer.endElement(keyQName.getNamespaceURI(), keyQName.getLocalName(), keyQName - .toPrefixString(namespaceService)); - } - - if (personService.personExists(uid)) - { - String uguid = personService.getPerson(uid).getId(); - - writer.startElement(nodeUUID.getNamespaceURI(), nodeUUID.getLocalName(), nodeUUID - .toPrefixString(namespaceService), new AttributesImpl()); - - writer.characters(uguid.toCharArray(), 0, uguid.length()); - - writer.endElement(nodeUUID.getNamespaceURI(), nodeUUID.getLocalName(), nodeUUID - .toPrefixString(namespaceService)); - } - writer.endElement(ContentModel.TYPE_PERSON.getNamespaceURI(), ContentModel.TYPE_PERSON - .getLocalName(), ContentModel.TYPE_PERSON.toPrefixString(namespaceService)); - - } - - } - catch (NamingException e) - { - throw new ExportSourceImporterException("Failed to import people.", e); - } - finally - { - if (ctx != null) - { - try - { - ctx.close(); - } - catch (NamingException e) - { - throw new ExportSourceImporterException("Failed to import people.", e); - } - } - } - - for (String prefix : prefixes) - { - if (!prefix.equals("xml")) - { - writer.endPrefixMapping(prefix); - } - } - - writer.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view", NamespaceService.REPOSITORY_VIEW_PREFIX - + ":" + "view"); - - writer.endDocument(); - } - catch (SAXException e) - { - throw new ExportSourceImporterException("Failed to create file for import.", e); - } - } - - public static void main(String[] args) throws Exception - { - ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); - ExportSource source = (ExportSource) ctx.getBean("ldapPeopleExportSource"); - TransactionService txs = (TransactionService) ctx.getBean("transactionComponent"); - UserTransaction tx = txs.getUserTransaction(); - tx.begin(); - - File file = new File(args[0]); - Writer writer = new BufferedWriter(new FileWriter(file)); - XMLWriter xmlWriter = createXMLExporter(writer); - source.generateExport(xmlWriter); - xmlWriter.close(); - - tx.commit(); - } - - private static XMLWriter createXMLExporter(Writer writer) - { - // Define output format - OutputFormat format = OutputFormat.createPrettyPrint(); - format.setNewLineAfterDeclaration(false); - format.setIndentSize(3); - format.setEncoding("UTF-8"); - - // Construct an XML Exporter - - XMLWriter xmlWriter = new XMLWriter(writer, format); - return xmlWriter; - } -} +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.security.authentication.ldap; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.Writer; +import java.util.Collection; +import java.util.Map; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.InitialDirContext; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; +import javax.transaction.UserTransaction; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.importer.ExportSource; +import org.alfresco.repo.importer.ExportSourceImporterException; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.dom4j.io.OutputFormat; +import org.dom4j.io.XMLWriter; +import org.springframework.context.ApplicationContext; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +public class LDAPPersonExportSource implements ExportSource +{ + private static Log s_logger = LogFactory.getLog(LDAPPersonExportSource.class); + + private String personQuery = "(objectclass=inetOrgPerson)"; + + private String searchBase; + + private String userIdAttributeName; + + private LDAPInitialDirContextFactory ldapInitialContextFactory; + + private PersonService personService; + + private Map attributeMapping; + + private NamespaceService namespaceService; + + private Map attributeDefaults; + + private boolean errorOnMissingUID; + + public LDAPPersonExportSource() + { + super(); + } + + public void setPersonQuery(String personQuery) + { + this.personQuery = personQuery; + } + + public void setSearchBase(String searchBase) + { + this.searchBase = searchBase; + } + + public void setUserIdAttributeName(String userIdAttributeName) + { + this.userIdAttributeName = userIdAttributeName; + } + + public void setLDAPInitialDirContextFactory(LDAPInitialDirContextFactory ldapInitialDirContextFactory) + { + this.ldapInitialContextFactory = ldapInitialDirContextFactory; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setAttributeDefaults(Map attributeDefaults) + { + this.attributeDefaults = attributeDefaults; + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + public void setAttributeMapping(Map attributeMapping) + { + this.attributeMapping = attributeMapping; + } + + public void setErrorOnMissingUID(boolean errorOnMissingUID) + { + this.errorOnMissingUID = errorOnMissingUID; + } + + public void generateExport(XMLWriter writer) + { + QName nodeUUID = QName.createQName("sys:node-uuid", namespaceService); + + Collection prefixes = namespaceService.getPrefixes(); + QName childQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "childName", namespaceService); + + try + { + AttributesImpl attrs = new AttributesImpl(); + attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, childQName.getLocalName(), childQName + .toPrefixString(), null, ContentModel.TYPE_PERSON.toPrefixString(namespaceService)); + + writer.startDocument(); + + for (String prefix : prefixes) + { + if (!prefix.equals("xml")) + { + String uri = namespaceService.getNamespaceURI(prefix); + writer.startPrefixMapping(prefix, uri); + } + } + + writer.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view", + NamespaceService.REPOSITORY_VIEW_PREFIX + ":" + "view", new AttributesImpl()); + + InitialDirContext ctx = null; + try + { + ctx = ldapInitialContextFactory.getDefaultIntialDirContext(); + + // Authentication has been successful. + // Set the current user, they are now authenticated. + + SearchControls userSearchCtls = new SearchControls(); + userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); + + userSearchCtls.setCountLimit(Integer.MAX_VALUE); + + NamingEnumeration searchResults = ctx.search(searchBase, personQuery, userSearchCtls); + while (searchResults.hasMoreElements()) + { + SearchResult result = (SearchResult) searchResults.next(); + Attributes attributes = result.getAttributes(); + Attribute uidAttribute = attributes.get(userIdAttributeName); + if (uidAttribute == null) + { + if(errorOnMissingUID) + { + throw new ExportSourceImporterException( + "User returned by user search does not have mandatory user id attribute " + attributes); + } + else + { + s_logger.warn("User returned by user search does not have mandatory user id attribute " + attributes); + continue; + } + } + String uid = (String) uidAttribute.get(0); + + if (s_logger.isDebugEnabled()) + { + s_logger.debug("Adding user for " + uid); + } + + + writer.startElement(ContentModel.TYPE_PERSON.getNamespaceURI(), ContentModel.TYPE_PERSON + .getLocalName(), ContentModel.TYPE_PERSON.toPrefixString(namespaceService), attrs); + + // permissions + + // owner + + writer.startElement(ContentModel.ASPECT_OWNABLE.getNamespaceURI(), ContentModel.ASPECT_OWNABLE + .getLocalName(), ContentModel.ASPECT_OWNABLE.toPrefixString(namespaceService), + new AttributesImpl()); + + writer.endElement(ContentModel.ASPECT_OWNABLE.getNamespaceURI(), ContentModel.ASPECT_OWNABLE + .getLocalName(), ContentModel.ASPECT_OWNABLE.toPrefixString(namespaceService)); + + writer.startElement(ContentModel.PROP_OWNER.getNamespaceURI(), ContentModel.PROP_OWNER + .getLocalName(), ContentModel.PROP_OWNER.toPrefixString(namespaceService), + new AttributesImpl()); + + writer.characters(uid.toCharArray(), 0, uid.length()); + + writer.endElement(ContentModel.PROP_OWNER.getNamespaceURI(), + ContentModel.PROP_OWNER.getLocalName(), ContentModel.PROP_OWNER + .toPrefixString(namespaceService)); + + for (String key : attributeMapping.keySet()) + { + QName keyQName = QName.createQName(key, namespaceService); + + writer.startElement(keyQName.getNamespaceURI(), keyQName.getLocalName(), keyQName + .toPrefixString(namespaceService), new AttributesImpl()); + + // cater for null + String attributeName = attributeMapping.get(key); + if (attributeName != null) + { + Attribute attribute = attributes.get(attributeName); + if (attribute != null) + { + String value = (String) attribute.get(0); + if (value != null) + { + writer.characters(value.toCharArray(), 0, value.length()); + } + } + else + { + String defaultValue = attributeDefaults.get(key); + if(defaultValue != null) + { + writer.characters(defaultValue.toCharArray(), 0, defaultValue.length()); + } + } + } + else + { + String defaultValue = attributeDefaults.get(key); + if(defaultValue != null) + { + writer.characters(defaultValue.toCharArray(), 0, defaultValue.length()); + } + } + + writer.endElement(keyQName.getNamespaceURI(), keyQName.getLocalName(), keyQName + .toPrefixString(namespaceService)); + } + + if (personService.personExists(uid)) + { + String uguid = personService.getPerson(uid).getId(); + + writer.startElement(nodeUUID.getNamespaceURI(), nodeUUID.getLocalName(), nodeUUID + .toPrefixString(namespaceService), new AttributesImpl()); + + writer.characters(uguid.toCharArray(), 0, uguid.length()); + + writer.endElement(nodeUUID.getNamespaceURI(), nodeUUID.getLocalName(), nodeUUID + .toPrefixString(namespaceService)); + } + writer.endElement(ContentModel.TYPE_PERSON.getNamespaceURI(), ContentModel.TYPE_PERSON + .getLocalName(), ContentModel.TYPE_PERSON.toPrefixString(namespaceService)); + + } + + } + catch (NamingException e) + { + throw new ExportSourceImporterException("Failed to import people.", e); + } + finally + { + if (ctx != null) + { + try + { + ctx.close(); + } + catch (NamingException e) + { + throw new ExportSourceImporterException("Failed to import people.", e); + } + } + } + + for (String prefix : prefixes) + { + if (!prefix.equals("xml")) + { + writer.endPrefixMapping(prefix); + } + } + + writer.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view", NamespaceService.REPOSITORY_VIEW_PREFIX + + ":" + "view"); + + writer.endDocument(); + } + catch (SAXException e) + { + throw new ExportSourceImporterException("Failed to create file for import.", e); + } + } + + public static void main(String[] args) throws Exception + { + ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + ExportSource source = (ExportSource) ctx.getBean("ldapPeopleExportSource"); + TransactionService txs = (TransactionService) ctx.getBean("transactionComponent"); + UserTransaction tx = txs.getUserTransaction(); + tx.begin(); + + File file = new File(args[0]); + Writer writer = new BufferedWriter(new FileWriter(file)); + XMLWriter xmlWriter = createXMLExporter(writer); + source.generateExport(xmlWriter); + xmlWriter.close(); + + tx.commit(); + } + + private static XMLWriter createXMLExporter(Writer writer) + { + // Define output format + OutputFormat format = OutputFormat.createPrettyPrint(); + format.setNewLineAfterDeclaration(false); + format.setIndentSize(3); + format.setEncoding("UTF-8"); + + // Construct an XML Exporter + + XMLWriter xmlWriter = new XMLWriter(writer, format); + return xmlWriter; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/security/permissions/dynamic/LockOwnerDynamicAuthority.java b/source/java/org/alfresco/repo/security/permissions/dynamic/LockOwnerDynamicAuthority.java index 1549425147..0713337710 100644 --- a/source/java/org/alfresco/repo/security/permissions/dynamic/LockOwnerDynamicAuthority.java +++ b/source/java/org/alfresco/repo/security/permissions/dynamic/LockOwnerDynamicAuthority.java @@ -16,6 +16,8 @@ */ package org.alfresco.repo.security.permissions.dynamic; +import java.io.Serializable; + import org.alfresco.model.ContentModel; import org.alfresco.repo.security.permissions.DynamicAuthority; import org.alfresco.service.cmr.lock.LockService; @@ -44,9 +46,13 @@ public class LockOwnerDynamicAuthority implements DynamicAuthority, Initializing } if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY)) { - NodeRef original = DefaultTypeConverter.INSTANCE.convert( - NodeRef.class, nodeService.getProperty(nodeRef, ContentModel.PROP_COPY_REFERENCE)); - if (nodeService.exists(original)) + NodeRef original = null; + Serializable reference = nodeService.getProperty(nodeRef, ContentModel.PROP_COPY_REFERENCE); + if (reference != null) + { + original = DefaultTypeConverter.INSTANCE.convert(NodeRef.class, reference); + } + if (original != null && nodeService.exists(original)) { return (lockService.getLockStatus(original) == LockStatus.LOCK_OWNER); } diff --git a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java index eb07041b43..656967357c 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java @@ -1,1132 +1,1132 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.repo.security.permissions.impl; - -import java.io.Serializable; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import net.sf.acegisecurity.Authentication; -import net.sf.acegisecurity.GrantedAuthority; -import net.sf.acegisecurity.providers.dao.User; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.cache.SimpleCache; -import org.alfresco.repo.policy.JavaBehaviour; -import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.permissions.DynamicAuthority; -import org.alfresco.repo.security.permissions.NodePermissionEntry; -import org.alfresco.repo.security.permissions.PermissionEntry; -import org.alfresco.repo.security.permissions.PermissionReference; -import org.alfresco.repo.security.permissions.PermissionServiceSPI; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.security.AccessPermission; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.AuthorityService; -import org.alfresco.service.cmr.security.AuthorityType; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.EqualsHelper; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.InitializingBean; - -/** - * The Alfresco implementation of a permissions service against our APIs for the - * permissions model and permissions persistence. - * - * @author andyh - */ -public class PermissionServiceImpl implements PermissionServiceSPI, InitializingBean -{ - - static SimplePermissionReference OLD_ALL_PERMISSIONS_REFERENCE = new SimplePermissionReference( - QName.createQName("", PermissionService.ALL_PERMISSIONS), - PermissionService.ALL_PERMISSIONS); - - private static Log log = LogFactory.getLog(PermissionServiceImpl.class); - - /** a transactionally-safe cache to be injected */ - private SimpleCache accessCache; - - /* - * Access to the model - */ - private ModelDAO modelDAO; - - /* - * Access to permissions - */ - private PermissionsDaoComponent permissionsDaoComponent; - - /* - * Access to the node service - */ - private NodeService nodeService; - - /* - * Access to the data dictionary - */ - private DictionaryService dictionaryService; - - /* - * Access to the authentication component - */ - private AuthenticationComponent authenticationComponent; - - /* - * Access to the authority component - */ - private AuthorityService authorityService; - - /* - * Dynamic authorities providers - */ - private List dynamicAuthorities; - - private PolicyComponent policyComponent; - - /* - * Standard spring construction. - */ - public PermissionServiceImpl() - { - super(); - } - - // - // Inversion of control - // - - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - public void setModelDAO(ModelDAO modelDAO) - { - this.modelDAO = modelDAO; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setPermissionsDaoComponent(PermissionsDaoComponent permissionsDaoComponent) - { - this.permissionsDaoComponent = permissionsDaoComponent; - } - - public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) - { - this.authenticationComponent = authenticationComponent; - } - - public void setAuthorityService(AuthorityService authorityService) - { - this.authorityService = authorityService; - } - - public void setDynamicAuthorities(List dynamicAuthorities) - { - this.dynamicAuthorities = dynamicAuthorities; - } - - /** - * Set the permissions access cache. - * - * @param accessCache - * a transactionally safe cache - */ - public void setAccessCache(SimpleCache accessCache) - { - this.accessCache = accessCache; - } - - public void setPolicyComponent(PolicyComponent policyComponent) - { - this.policyComponent = policyComponent; - } - - public void onMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef) - { - accessCache.clear(); - } - - public void afterPropertiesSet() throws Exception - { - if (dictionaryService == null) - { - throw new IllegalArgumentException("Property 'dictionaryService' has not been set"); - } - if (modelDAO == null) - { - throw new IllegalArgumentException("Property 'modelDAO' has not been set"); - } - if (nodeService == null) - { - throw new IllegalArgumentException("Property 'nodeService' has not been set"); - } - if (permissionsDaoComponent == null) - { - throw new IllegalArgumentException("Property 'permissionsDAO' has not been set"); - } - if (authenticationComponent == null) - { - throw new IllegalArgumentException("Property 'authenticationComponent' has not been set"); - } - if(authorityService == null) - { - throw new IllegalArgumentException("Property 'authorityService' has not been set"); - } - if (accessCache == null) - { - throw new IllegalArgumentException("Property 'accessCache' has not been set"); - } - if (policyComponent == null) - { - throw new IllegalArgumentException("Property 'policyComponent' has not been set"); - } - - policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onMoveNode"), ContentModel.TYPE_BASE, new JavaBehaviour(this, "onMoveNode")); - - } - - // - // Permissions Service - // - - public String getOwnerAuthority() - { - return OWNER_AUTHORITY; - } - - public String getAllAuthorities() - { - return ALL_AUTHORITIES; - } - - public String getAllPermission() - { - return ALL_PERMISSIONS; - } - - public Set getPermissions(NodeRef nodeRef) - { - return getAllPermissionsImpl(nodeRef, true, true); - } - - public Set getAllSetPermissions(NodeRef nodeRef) - { - HashSet accessPermissions = new HashSet(); - NodePermissionEntry nodePremissionEntry = getSetPermissions(nodeRef); - for (PermissionEntry pe : nodePremissionEntry.getPermissionEntries()) - { - accessPermissions.add(new AccessPermissionImpl(getPermission(pe.getPermissionReference()), pe - .getAccessStatus(), pe.getAuthority())); - } - return accessPermissions; - } - - private Set getAllPermissionsImpl(NodeRef nodeRef, boolean includeTrue, boolean includeFalse) - { - String userName = authenticationComponent.getCurrentUserName(); - HashSet accessPermissions = new HashSet(); - for (PermissionReference pr : getSettablePermissionReferences(nodeRef)) - { - if (hasPermission(nodeRef, pr) == AccessStatus.ALLOWED) - { - accessPermissions.add(new AccessPermissionImpl(getPermission(pr), AccessStatus.ALLOWED, userName)); - } - else - { - if (includeFalse) - { - accessPermissions.add(new AccessPermissionImpl(getPermission(pr), AccessStatus.DENIED, userName)); - } - } - } - return accessPermissions; - } - - private class AccessPermissionImpl implements AccessPermission - { - private String permission; - - private AccessStatus accessStatus; - - private String authority; - - private AuthorityType authorityType; - - AccessPermissionImpl(String permission, AccessStatus accessStatus, String authority) - { - this.permission = permission; - this.accessStatus = accessStatus; - this.authority = authority; - this.authorityType = AuthorityType.getAuthorityType(authority); - } - - public String getPermission() - { - return permission; - } - - public AccessStatus getAccessStatus() - { - return accessStatus; - } - - public String getAuthority() - { - return authority; - } - - public AuthorityType getAuthorityType() - { - return authorityType; - } - - @Override - public String toString() - { - return accessStatus + " " + this.permission + " - " + - this.authority + " (" + this.authorityType + ")"; - } - - @Override - public boolean equals(Object o) - { - if (this == o) - { - return true; - } - if (!(o instanceof AccessPermissionImpl)) - { - return false; - } - AccessPermissionImpl other = (AccessPermissionImpl) o; - return this.getPermission().equals(other.getPermission()) - && (this.getAccessStatus() == other.getAccessStatus() && (this.getAccessStatus().equals(other - .getAccessStatus()))); - } - - @Override - public int hashCode() - { - return ((authority.hashCode() * 37) + permission.hashCode()) * 37 + accessStatus.hashCode(); - } - } - - public Set getSettablePermissions(NodeRef nodeRef) - { - Set settable = getSettablePermissionReferences(nodeRef); - Set strings = new HashSet(settable.size()); - for (PermissionReference pr : settable) - { - strings.add(getPermission(pr)); - } - return strings; - } - - public Set getSettablePermissions(QName type) - { - Set settable = getSettablePermissionReferences(type); - Set strings = new LinkedHashSet(settable.size()); - for (PermissionReference pr : settable) - { - strings.add(getPermission(pr)); - } - return strings; - } - - public NodePermissionEntry getSetPermissions(NodeRef nodeRef) - { - return permissionsDaoComponent.getPermissions(nodeRef); - } - - public AccessStatus hasPermission(NodeRef nodeRef, PermissionReference perm) - { - // If the node ref is null there is no sensible test to do - and there - // must be no permissions - // - so we allow it - if (nodeRef == null) - { - return AccessStatus.ALLOWED; - } - - // If the permission is null we deny - if (perm == null) - { - return AccessStatus.DENIED; - } - - // Allow permissions for nodes that do not exist - if (!nodeService.exists(nodeRef)) - { - return AccessStatus.ALLOWED; - } - - // Get the current authentications - // Use the smart authentication cache to improve permissions performance - Authentication auth = authenticationComponent.getCurrentAuthentication(); - Set authorisations = getAuthorisations(auth, nodeRef); - Serializable key = generateKey( - authorisations, - nodeRef, - perm); - AccessStatus status = accessCache.get(key); - if (status != null) - { - return status; - } - - // If the node does not support the given permission there is no point - // doing the test - Set available = modelDAO.getAllPermissions(nodeRef); - available.add(getAllPermissionReference()); - available.add(OLD_ALL_PERMISSIONS_REFERENCE); - - if (!(available.contains(perm))) - { - accessCache.put(key, AccessStatus.DENIED); - return AccessStatus.DENIED; - } - - // - // TODO: Dynamic permissions via evaluators - // - - /* - * Does the current authentication have the supplied permission on the - * given node. - */ - - QName typeQname = nodeService.getType(nodeRef); - Set aspectQNames = nodeService.getAspects(nodeRef); - - if (perm.equals(OLD_ALL_PERMISSIONS_REFERENCE)) - { - perm = getAllPermissionReference(); - } - NodeTest nt = new NodeTest(perm, typeQname, aspectQNames); - boolean result = nt.evaluate(authorisations, nodeRef); - if (log.isDebugEnabled()) - { - log.debug("Permission <" - + perm + "> is " + (result ? "allowed" : "denied") + " for " - + authenticationComponent.getCurrentUserName() + " on node " + nodeService.getPath(nodeRef)); - } - - status = result ? AccessStatus.ALLOWED : AccessStatus.DENIED; - accessCache.put(key, status); - return status; - } - - /** - * Key for a cache object is built from all the known Authorities (which can - * change dynamically so they must all be used) the NodeRef ID and the - * permission reference itself. This gives a unique key for each permission - * test. - */ - static Serializable generateKey(Set auths, NodeRef nodeRef, PermissionReference perm) - { - LinkedHashSet key = new LinkedHashSet(); - key.add(perm.toString()); - key.addAll(auths); - key.add(nodeRef); - return key; - } - - /** - * Get the authorisations for the currently authenticated user - * - * @param auth - * @return - */ - private Set getAuthorisations(Authentication auth, NodeRef nodeRef) - { - HashSet auths = new HashSet(); - // No authenticated user then no permissions - if (auth == null) - { - return auths; - } - // TODO: Refactor and use the authentication service for this. - User user = (User) auth.getPrincipal(); - auths.add(user.getUsername()); - for (GrantedAuthority authority : auth.getAuthorities()) - { - auths.add(authority.getAuthority()); - } - if (dynamicAuthorities != null) - { - for (DynamicAuthority da : dynamicAuthorities) - { - if (da.hasAuthority(nodeRef, user.getUsername())) - { - auths.add(da.getAuthority()); - } - } - } - auths.addAll(authorityService.getAuthorities()); - return auths; - } - - public NodePermissionEntry explainPermission(NodeRef nodeRef, PermissionReference perm) - { - // TODO Auto-generated method stub - return null; - } - - public void deletePermissions(NodeRef nodeRef) - { - permissionsDaoComponent.deletePermissions(nodeRef); - accessCache.clear(); - } - - public void deletePermissions(NodePermissionEntry nodePermissionEntry) - { - permissionsDaoComponent.deletePermissions(nodePermissionEntry.getNodeRef()); - accessCache.clear(); - } - - /** - * @see #deletePermission(NodeRef, String, PermissionReference) - */ - public void deletePermission(PermissionEntry permissionEntry) - { - NodeRef nodeRef = permissionEntry.getNodeRef(); - String authority = permissionEntry.getAuthority(); - PermissionReference permission = permissionEntry.getPermissionReference(); - deletePermission(nodeRef, authority, permission); - } - - public void deletePermission(NodeRef nodeRef, String authority, PermissionReference perm) - { - permissionsDaoComponent.deletePermission(nodeRef, authority, perm); - accessCache.clear(); - } - - public void clearPermission(NodeRef nodeRef, String authority) - { - permissionsDaoComponent.deletePermissions(nodeRef, authority); - accessCache.clear(); - } - - public void setPermission(NodeRef nodeRef, String authority, PermissionReference perm, boolean allow) - { - permissionsDaoComponent.setPermission(nodeRef, authority, perm, allow); - accessCache.clear(); - } - - public void setPermission(PermissionEntry permissionEntry) - { - permissionsDaoComponent.setPermission(permissionEntry); - accessCache.clear(); - } - - public void setPermission(NodePermissionEntry nodePermissionEntry) - { - permissionsDaoComponent.setPermission(nodePermissionEntry); - accessCache.clear(); - } - - public void setInheritParentPermissions(NodeRef nodeRef, boolean inheritParentPermissions) - { - permissionsDaoComponent.setInheritParentPermissions(nodeRef, inheritParentPermissions); - accessCache.clear(); - } - - /** - * @see org.alfresco.service.cmr.security.PermissionService#getInheritParentPermissions(org.alfresco.service.cmr.repository.NodeRef) - */ - public boolean getInheritParentPermissions(NodeRef nodeRef) - { - return permissionsDaoComponent.getInheritParentPermissions(nodeRef); - } - - - public PermissionReference getPermissionReference(QName qname, String permissionName) - { - return modelDAO.getPermissionReference(qname, permissionName); - } - - public PermissionReference getAllPermissionReference() - { - return getPermissionReference(ALL_PERMISSIONS); - } - - public String getPermission(PermissionReference permissionReference) - { - if (modelDAO.isUnique(permissionReference)) - { - return permissionReference.getName(); - } - else - { - return permissionReference.toString(); - } - } - - public PermissionReference getPermissionReference(String permissionName) - { - return modelDAO.getPermissionReference(null, permissionName); - } - - public Set getSettablePermissionReferences(QName type) - { - return modelDAO.getExposedPermissions(type); - } - - public Set getSettablePermissionReferences(NodeRef nodeRef) - { - return modelDAO.getExposedPermissions(nodeRef); - } - - public void deletePermission(NodeRef nodeRef, String authority, String perm) - { - deletePermission(nodeRef, authority, getPermissionReference(perm)); - } - - public AccessStatus hasPermission(NodeRef nodeRef, String perm) - { - return hasPermission(nodeRef, getPermissionReference(perm)); - } - - public void setPermission(NodeRef nodeRef, String authority, String perm, boolean allow) - { - setPermission(nodeRef, authority, getPermissionReference(perm), allow); - } - - public void deletePermissions(String recipient) - { - permissionsDaoComponent.deletePermissions(recipient); - accessCache.clear(); - } - - // - // SUPPORT CLASSES - // - - /** - * Support class to test the permission on a node. - * - * @author Andy Hind - */ - private class NodeTest - { - /* - * The required permission. - */ - PermissionReference required; - - /* - * Granters of the permission - */ - Set granters; - - /* - * The additional permissions required at the node level. - */ - Set nodeRequirements = new HashSet(); - - /* - * The additional permissions required on the parent. - */ - Set parentRequirements = new HashSet(); - - /* - * The permissions required on all children . - */ - Set childrenRequirements = new HashSet(); - - /* - * The type name of the node. - */ - QName typeQName; - - /* - * The aspects set on the node. - */ - Set aspectQNames; - - /* - * Constructor just gets the additional requirements - */ - NodeTest(PermissionReference required, QName typeQName, Set aspectQNames) - { - this.required = required; - this.typeQName = typeQName; - this.aspectQNames = aspectQNames; - - // Set the required node permissions - nodeRequirements = modelDAO.getRequiredPermissions(required, typeQName, aspectQNames, - RequiredPermission.On.NODE); - - parentRequirements = modelDAO.getRequiredPermissions(required, typeQName, aspectQNames, - RequiredPermission.On.PARENT); - - childrenRequirements = modelDAO.getRequiredPermissions(required, typeQName, aspectQNames, - RequiredPermission.On.CHILDREN); - - // Find all the permissions that grant the allowed permission - // All permissions are treated specially. - granters = modelDAO.getGrantingPermissions(required); - granters.add(getAllPermissionReference()); - granters.add(OLD_ALL_PERMISSIONS_REFERENCE); - } - - /** - * External hook point - * - * @param authorisations - * @param nodeRef - * @return - */ - boolean evaluate(Set authorisations, NodeRef nodeRef) - { - Set> denied = new HashSet>(); - return evaluate(authorisations, nodeRef, denied, null); - } - - /** - * Internal hook point for recursion - * - * @param authorisations - * @param nodeRef - * @param denied - * @param recursiveIn - * @return - */ - boolean evaluate(Set authorisations, NodeRef nodeRef, Set> denied, - MutableBoolean recursiveIn) - { - // Do we defer our required test to a parent (yes if not null) - MutableBoolean recursiveOut = null; - - Set> locallyDenied = new HashSet>(); - locallyDenied.addAll(denied); - locallyDenied.addAll(getDenied(nodeRef)); - - // Start out true and "and" all other results - boolean success = true; - - // Check the required permissions but not for sets they rely on - // their underlying permissions - if (required.equals(getPermissionReference(ALL_PERMISSIONS)) || modelDAO.checkPermission(required)) - { - if (parentRequirements.contains(required)) - { - if (checkGlobalPermissions(authorisations) || checkRequired(authorisations, nodeRef, locallyDenied)) - { - // No need to do the recursive test as it has been found - recursiveOut = null; - if (recursiveIn != null) - { - recursiveIn.setValue(true); - } - } - else - { - // Much cheaper to do this as we go then check all the - // stack values for each parent - recursiveOut = new MutableBoolean(false); - } - } - else - { - // We have to do the test as no parent will help us out - success &= hasSinglePermission(authorisations, nodeRef); - } - if (!success) - { - return false; - } - } - - // Check the other permissions required on the node - for (PermissionReference pr : nodeRequirements) - { - // Build a new test - NodeTest nt = new NodeTest(pr, typeQName, aspectQNames); - success &= nt.evaluate(authorisations, nodeRef, locallyDenied, null); - if (!success) - { - return false; - } - } - - // Check the permission required of the parent - - if (success) - { - ChildAssociationRef car = nodeService.getPrimaryParent(nodeRef); - if (car.getParentRef() != null) - { - - NodePermissionEntry nodePermissions = permissionsDaoComponent.getPermissions(car.getChildRef()); - if ((nodePermissions == null) || (nodePermissions.inheritPermissions())) - { - - locallyDenied.addAll(getDenied(car.getParentRef())); - for (PermissionReference pr : parentRequirements) - { - if (pr.equals(required)) - { - // Recursive permission - success &= this.evaluate(authorisations, car.getParentRef(), locallyDenied, - recursiveOut); - if ((recursiveOut != null) && recursiveOut.getValue()) - { - if (recursiveIn != null) - { - recursiveIn.setValue(true); - } - } - } - else - { - NodeTest nt = new NodeTest(pr, typeQName, aspectQNames); - success &= nt.evaluate(authorisations, car.getParentRef(), locallyDenied, null); - } - - if (!success) - { - return false; - } - } - } - } - } - - if ((recursiveOut != null) && (!recursiveOut.getValue())) - { - // The required authentication was not resolved in recursion - return false; - } - - // Check permissions required of children - if (childrenRequirements.size() > 0) - { - List childAssocRefs = nodeService.getChildAssocs(nodeRef); - for (PermissionReference pr : childrenRequirements) - { - for (ChildAssociationRef child : childAssocRefs) - { - success &= (hasPermission(child.getChildRef(), pr) == AccessStatus.ALLOWED); - if (!success) - { - return false; - } - } - } - } - - return success; - } - - public boolean hasSinglePermission(Set authorisations, NodeRef nodeRef) - { - // Check global permission - - if (checkGlobalPermissions(authorisations)) - { - return true; - } - - Set> denied = new HashSet>(); - - // Keep track of permission that are denied - - // Permissions are only evaluated up the primary parent chain - // TODO: Do not ignore non primary permissions - ChildAssociationRef car = nodeService.getPrimaryParent(nodeRef); - // Work up the parent chain evaluating permissions. - while (car != null) - { - // Add any denied permission to the denied list - these can not - // then - // be used to given authentication. - // A -> B -> C - // If B denies all permissions to any - allowing all permissions - // to - // andy at node A has no effect - - denied.addAll(getDenied(car.getChildRef())); - - // If the current node allows the permission we are done - // The test includes any parent or ancestor requirements - if (checkRequired(authorisations, car.getChildRef(), denied)) - { - return true; - } - - // Build the next element of the evaluation chain - if (car.getParentRef() != null) - { - NodePermissionEntry nodePermissions = permissionsDaoComponent.getPermissions(car.getChildRef()); - if ((nodePermissions == null) || (nodePermissions.inheritPermissions())) - { - car = nodeService.getPrimaryParent(car.getParentRef()); - } - else - { - car = null; - } - } - else - { - car = null; - } - - } - - // TODO: Support meta data permissions on the root node? - - return false; - - } - - /** - * Check if we have a global permission - * - * @param authorisations - * @return - */ - private boolean checkGlobalPermissions(Set authorisations) - { - for (PermissionEntry pe : modelDAO.getGlobalPermissionEntries()) - { - if (isGranted(pe, authorisations, null)) - { - return true; - } - } - return false; - } - - /** - * Get the list of permissions denied for this node. - * - * @param nodeRef - * @return - */ - Set> getDenied(NodeRef nodeRef) - { - Set> deniedSet = new HashSet>(); - - // Loop over all denied permissions - NodePermissionEntry nodeEntry = permissionsDaoComponent.getPermissions(nodeRef); - if (nodeEntry != null) - { - for (PermissionEntry pe : nodeEntry.getPermissionEntries()) - { - if (pe.isDenied()) - { - // All the sets that grant this permission must be - // denied - // Note that granters includes the orginal permission - Set granters = modelDAO - .getGrantingPermissions(pe.getPermissionReference()); - for (PermissionReference granter : granters) - { - deniedSet.add(new Pair(pe.getAuthority(), granter)); - } - - // All the things granted by this permission must be - // denied - Set grantees = modelDAO.getGranteePermissions(pe.getPermissionReference()); - for (PermissionReference grantee : grantees) - { - deniedSet.add(new Pair(pe.getAuthority(), grantee)); - } - - // All permission excludes all permissions available for - // the node. - if (pe.getPermissionReference().equals(getAllPermissionReference()) || pe.getPermissionReference().equals(OLD_ALL_PERMISSIONS_REFERENCE)) - { - for (PermissionReference deny : modelDAO.getAllPermissions(nodeRef)) - { - deniedSet.add(new Pair(pe.getAuthority(), deny)); - } - } - } - } - } - return deniedSet; - } - - /** - * Check that a given authentication is available on a node - * - * @param authorisations - * @param nodeRef - * @param denied - * @return - */ - boolean checkRequired(Set authorisations, NodeRef nodeRef, Set> denied) - { - NodePermissionEntry nodeEntry = permissionsDaoComponent.getPermissions(nodeRef); - - // No permissions set - short cut to deny - if (nodeEntry == null) - { - return false; - } - - // Check if each permission allows - the first wins. - // We could have other voting style mechanisms here - for (PermissionEntry pe : nodeEntry.getPermissionEntries()) - { - if (isGranted(pe, authorisations, denied)) - { - return true; - } - } - return false; - } - - /** - * Is a permission granted - * - * @param pe - - * the permissions entry to consider - * @param granters - - * the set of granters - * @param authorisations - - * the set of authorities - * @param denied - - * the set of denied permissions/authority pais - * @return - */ - private boolean isGranted(PermissionEntry pe, Set authorisations, - Set> denied) - { - // If the permission entry denies then we just deny - if (pe.isDenied()) - { - return false; - } - - // The permission is allowed but we deny it as it is in the denied - // set - if (denied != null) - { - Pair specific = new Pair(pe.getAuthority(), - required); - if (denied.contains(specific)) - { - return false; - } - } - - // If the permission has a match in both the authorities and - // granters list it is allowed - // It applies to the current user and it is granted - if (authorisations.contains(pe.getAuthority()) && granters.contains(pe.getPermissionReference())) - { - { - return true; - } - } - - // Default deny - return false; - } - - } - - /** - * Helper class to store a pair of objects which may be null - * - * @author Andy Hind - */ - private static class Pair - { - A a; - - B b; - - Pair(A a, B b) - { - this.a = a; - this.b = b; - } - - A getA() - { - return a; - } - - B getB() - { - return b; - } - - @Override - public boolean equals(Object o) - { - if (this == o) - { - return true; - } - if (!(this instanceof Pair)) - { - return false; - } - Pair other = (Pair) o; - return EqualsHelper.nullSafeEquals(this.getA(), other.getA()) - && EqualsHelper.nullSafeEquals(this.getB(), other.getB()); - } - - @Override - public int hashCode() - { - return (((a == null) ? 0 : a.hashCode()) * 37) + ((b == null) ? 0 : b.hashCode()); - } - - } - - private static class MutableBoolean - { - private boolean value; - - MutableBoolean(boolean value) - { - this.value = value; - } - - void setValue(boolean value) - { - this.value = value; - } - - boolean getValue() - { - return value; - } - } -} +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.security.permissions.impl; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import net.sf.acegisecurity.Authentication; +import net.sf.acegisecurity.GrantedAuthority; +import net.sf.acegisecurity.providers.dao.User; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.permissions.DynamicAuthority; +import org.alfresco.repo.security.permissions.NodePermissionEntry; +import org.alfresco.repo.security.permissions.PermissionEntry; +import org.alfresco.repo.security.permissions.PermissionReference; +import org.alfresco.repo.security.permissions.PermissionServiceSPI; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.AccessPermission; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.EqualsHelper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; + +/** + * The Alfresco implementation of a permissions service against our APIs for the + * permissions model and permissions persistence. + * + * @author andyh + */ +public class PermissionServiceImpl implements PermissionServiceSPI, InitializingBean +{ + + static SimplePermissionReference OLD_ALL_PERMISSIONS_REFERENCE = new SimplePermissionReference( + QName.createQName("", PermissionService.ALL_PERMISSIONS), + PermissionService.ALL_PERMISSIONS); + + private static Log log = LogFactory.getLog(PermissionServiceImpl.class); + + /** a transactionally-safe cache to be injected */ + private SimpleCache accessCache; + + /* + * Access to the model + */ + private ModelDAO modelDAO; + + /* + * Access to permissions + */ + private PermissionsDaoComponent permissionsDaoComponent; + + /* + * Access to the node service + */ + private NodeService nodeService; + + /* + * Access to the data dictionary + */ + private DictionaryService dictionaryService; + + /* + * Access to the authentication component + */ + private AuthenticationComponent authenticationComponent; + + /* + * Access to the authority component + */ + private AuthorityService authorityService; + + /* + * Dynamic authorities providers + */ + private List dynamicAuthorities; + + private PolicyComponent policyComponent; + + /* + * Standard spring construction. + */ + public PermissionServiceImpl() + { + super(); + } + + // + // Inversion of control + // + + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + public void setModelDAO(ModelDAO modelDAO) + { + this.modelDAO = modelDAO; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setPermissionsDaoComponent(PermissionsDaoComponent permissionsDaoComponent) + { + this.permissionsDaoComponent = permissionsDaoComponent; + } + + public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) + { + this.authenticationComponent = authenticationComponent; + } + + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + + public void setDynamicAuthorities(List dynamicAuthorities) + { + this.dynamicAuthorities = dynamicAuthorities; + } + + /** + * Set the permissions access cache. + * + * @param accessCache + * a transactionally safe cache + */ + public void setAccessCache(SimpleCache accessCache) + { + this.accessCache = accessCache; + } + + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + public void onMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef) + { + accessCache.clear(); + } + + public void afterPropertiesSet() throws Exception + { + if (dictionaryService == null) + { + throw new IllegalArgumentException("Property 'dictionaryService' has not been set"); + } + if (modelDAO == null) + { + throw new IllegalArgumentException("Property 'modelDAO' has not been set"); + } + if (nodeService == null) + { + throw new IllegalArgumentException("Property 'nodeService' has not been set"); + } + if (permissionsDaoComponent == null) + { + throw new IllegalArgumentException("Property 'permissionsDAO' has not been set"); + } + if (authenticationComponent == null) + { + throw new IllegalArgumentException("Property 'authenticationComponent' has not been set"); + } + if(authorityService == null) + { + throw new IllegalArgumentException("Property 'authorityService' has not been set"); + } + if (accessCache == null) + { + throw new IllegalArgumentException("Property 'accessCache' has not been set"); + } + if (policyComponent == null) + { + throw new IllegalArgumentException("Property 'policyComponent' has not been set"); + } + + policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onMoveNode"), ContentModel.TYPE_BASE, new JavaBehaviour(this, "onMoveNode")); + + } + + // + // Permissions Service + // + + public String getOwnerAuthority() + { + return OWNER_AUTHORITY; + } + + public String getAllAuthorities() + { + return ALL_AUTHORITIES; + } + + public String getAllPermission() + { + return ALL_PERMISSIONS; + } + + public Set getPermissions(NodeRef nodeRef) + { + return getAllPermissionsImpl(nodeRef, true, true); + } + + public Set getAllSetPermissions(NodeRef nodeRef) + { + HashSet accessPermissions = new HashSet(); + NodePermissionEntry nodePremissionEntry = getSetPermissions(nodeRef); + for (PermissionEntry pe : nodePremissionEntry.getPermissionEntries()) + { + accessPermissions.add(new AccessPermissionImpl(getPermission(pe.getPermissionReference()), pe + .getAccessStatus(), pe.getAuthority())); + } + return accessPermissions; + } + + private Set getAllPermissionsImpl(NodeRef nodeRef, boolean includeTrue, boolean includeFalse) + { + String userName = authenticationComponent.getCurrentUserName(); + HashSet accessPermissions = new HashSet(); + for (PermissionReference pr : getSettablePermissionReferences(nodeRef)) + { + if (hasPermission(nodeRef, pr) == AccessStatus.ALLOWED) + { + accessPermissions.add(new AccessPermissionImpl(getPermission(pr), AccessStatus.ALLOWED, userName)); + } + else + { + if (includeFalse) + { + accessPermissions.add(new AccessPermissionImpl(getPermission(pr), AccessStatus.DENIED, userName)); + } + } + } + return accessPermissions; + } + + private class AccessPermissionImpl implements AccessPermission + { + private String permission; + + private AccessStatus accessStatus; + + private String authority; + + private AuthorityType authorityType; + + AccessPermissionImpl(String permission, AccessStatus accessStatus, String authority) + { + this.permission = permission; + this.accessStatus = accessStatus; + this.authority = authority; + this.authorityType = AuthorityType.getAuthorityType(authority); + } + + public String getPermission() + { + return permission; + } + + public AccessStatus getAccessStatus() + { + return accessStatus; + } + + public String getAuthority() + { + return authority; + } + + public AuthorityType getAuthorityType() + { + return authorityType; + } + + @Override + public String toString() + { + return accessStatus + " " + this.permission + " - " + + this.authority + " (" + this.authorityType + ")"; + } + + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (!(o instanceof AccessPermissionImpl)) + { + return false; + } + AccessPermissionImpl other = (AccessPermissionImpl) o; + return this.getPermission().equals(other.getPermission()) + && (this.getAccessStatus() == other.getAccessStatus() && (this.getAccessStatus().equals(other + .getAccessStatus()))); + } + + @Override + public int hashCode() + { + return ((authority.hashCode() * 37) + permission.hashCode()) * 37 + accessStatus.hashCode(); + } + } + + public Set getSettablePermissions(NodeRef nodeRef) + { + Set settable = getSettablePermissionReferences(nodeRef); + Set strings = new HashSet(settable.size()); + for (PermissionReference pr : settable) + { + strings.add(getPermission(pr)); + } + return strings; + } + + public Set getSettablePermissions(QName type) + { + Set settable = getSettablePermissionReferences(type); + Set strings = new LinkedHashSet(settable.size()); + for (PermissionReference pr : settable) + { + strings.add(getPermission(pr)); + } + return strings; + } + + public NodePermissionEntry getSetPermissions(NodeRef nodeRef) + { + return permissionsDaoComponent.getPermissions(nodeRef); + } + + public AccessStatus hasPermission(NodeRef nodeRef, PermissionReference perm) + { + // If the node ref is null there is no sensible test to do - and there + // must be no permissions + // - so we allow it + if (nodeRef == null) + { + return AccessStatus.ALLOWED; + } + + // If the permission is null we deny + if (perm == null) + { + return AccessStatus.DENIED; + } + + // Allow permissions for nodes that do not exist + if (!nodeService.exists(nodeRef)) + { + return AccessStatus.ALLOWED; + } + + // Get the current authentications + // Use the smart authentication cache to improve permissions performance + Authentication auth = authenticationComponent.getCurrentAuthentication(); + Set authorisations = getAuthorisations(auth, nodeRef); + Serializable key = generateKey( + authorisations, + nodeRef, + perm); + AccessStatus status = accessCache.get(key); + if (status != null) + { + return status; + } + + // If the node does not support the given permission there is no point + // doing the test + Set available = modelDAO.getAllPermissions(nodeRef); + available.add(getAllPermissionReference()); + available.add(OLD_ALL_PERMISSIONS_REFERENCE); + + if (!(available.contains(perm))) + { + accessCache.put(key, AccessStatus.DENIED); + return AccessStatus.DENIED; + } + + // + // TODO: Dynamic permissions via evaluators + // + + /* + * Does the current authentication have the supplied permission on the + * given node. + */ + + QName typeQname = nodeService.getType(nodeRef); + Set aspectQNames = nodeService.getAspects(nodeRef); + + if (perm.equals(OLD_ALL_PERMISSIONS_REFERENCE)) + { + perm = getAllPermissionReference(); + } + NodeTest nt = new NodeTest(perm, typeQname, aspectQNames); + boolean result = nt.evaluate(authorisations, nodeRef); + if (log.isDebugEnabled()) + { + log.debug("Permission <" + + perm + "> is " + (result ? "allowed" : "denied") + " for " + + authenticationComponent.getCurrentUserName() + " on node " + nodeService.getPath(nodeRef)); + } + + status = result ? AccessStatus.ALLOWED : AccessStatus.DENIED; + accessCache.put(key, status); + return status; + } + + /** + * Key for a cache object is built from all the known Authorities (which can + * change dynamically so they must all be used) the NodeRef ID and the + * permission reference itself. This gives a unique key for each permission + * test. + */ + static Serializable generateKey(Set auths, NodeRef nodeRef, PermissionReference perm) + { + LinkedHashSet key = new LinkedHashSet(); + key.add(perm.toString()); + key.addAll(auths); + key.add(nodeRef); + return key; + } + + /** + * Get the authorisations for the currently authenticated user + * + * @param auth + * @return + */ + private Set getAuthorisations(Authentication auth, NodeRef nodeRef) + { + HashSet auths = new HashSet(); + // No authenticated user then no permissions + if (auth == null) + { + return auths; + } + // TODO: Refactor and use the authentication service for this. + User user = (User) auth.getPrincipal(); + auths.add(user.getUsername()); + for (GrantedAuthority authority : auth.getAuthorities()) + { + auths.add(authority.getAuthority()); + } + if (dynamicAuthorities != null) + { + for (DynamicAuthority da : dynamicAuthorities) + { + if (da.hasAuthority(nodeRef, user.getUsername())) + { + auths.add(da.getAuthority()); + } + } + } + auths.addAll(authorityService.getAuthorities()); + return auths; + } + + public NodePermissionEntry explainPermission(NodeRef nodeRef, PermissionReference perm) + { + // TODO Auto-generated method stub + return null; + } + + public void deletePermissions(NodeRef nodeRef) + { + permissionsDaoComponent.deletePermissions(nodeRef); + accessCache.clear(); + } + + public void deletePermissions(NodePermissionEntry nodePermissionEntry) + { + permissionsDaoComponent.deletePermissions(nodePermissionEntry.getNodeRef()); + accessCache.clear(); + } + + /** + * @see #deletePermission(NodeRef, String, PermissionReference) + */ + public void deletePermission(PermissionEntry permissionEntry) + { + NodeRef nodeRef = permissionEntry.getNodeRef(); + String authority = permissionEntry.getAuthority(); + PermissionReference permission = permissionEntry.getPermissionReference(); + deletePermission(nodeRef, authority, permission); + } + + public void deletePermission(NodeRef nodeRef, String authority, PermissionReference perm) + { + permissionsDaoComponent.deletePermission(nodeRef, authority, perm); + accessCache.clear(); + } + + public void clearPermission(NodeRef nodeRef, String authority) + { + permissionsDaoComponent.deletePermissions(nodeRef, authority); + accessCache.clear(); + } + + public void setPermission(NodeRef nodeRef, String authority, PermissionReference perm, boolean allow) + { + permissionsDaoComponent.setPermission(nodeRef, authority, perm, allow); + accessCache.clear(); + } + + public void setPermission(PermissionEntry permissionEntry) + { + permissionsDaoComponent.setPermission(permissionEntry); + accessCache.clear(); + } + + public void setPermission(NodePermissionEntry nodePermissionEntry) + { + permissionsDaoComponent.setPermission(nodePermissionEntry); + accessCache.clear(); + } + + public void setInheritParentPermissions(NodeRef nodeRef, boolean inheritParentPermissions) + { + permissionsDaoComponent.setInheritParentPermissions(nodeRef, inheritParentPermissions); + accessCache.clear(); + } + + /** + * @see org.alfresco.service.cmr.security.PermissionService#getInheritParentPermissions(org.alfresco.service.cmr.repository.NodeRef) + */ + public boolean getInheritParentPermissions(NodeRef nodeRef) + { + return permissionsDaoComponent.getInheritParentPermissions(nodeRef); + } + + + public PermissionReference getPermissionReference(QName qname, String permissionName) + { + return modelDAO.getPermissionReference(qname, permissionName); + } + + public PermissionReference getAllPermissionReference() + { + return getPermissionReference(ALL_PERMISSIONS); + } + + public String getPermission(PermissionReference permissionReference) + { + if (modelDAO.isUnique(permissionReference)) + { + return permissionReference.getName(); + } + else + { + return permissionReference.toString(); + } + } + + public PermissionReference getPermissionReference(String permissionName) + { + return modelDAO.getPermissionReference(null, permissionName); + } + + public Set getSettablePermissionReferences(QName type) + { + return modelDAO.getExposedPermissions(type); + } + + public Set getSettablePermissionReferences(NodeRef nodeRef) + { + return modelDAO.getExposedPermissions(nodeRef); + } + + public void deletePermission(NodeRef nodeRef, String authority, String perm) + { + deletePermission(nodeRef, authority, getPermissionReference(perm)); + } + + public AccessStatus hasPermission(NodeRef nodeRef, String perm) + { + return hasPermission(nodeRef, getPermissionReference(perm)); + } + + public void setPermission(NodeRef nodeRef, String authority, String perm, boolean allow) + { + setPermission(nodeRef, authority, getPermissionReference(perm), allow); + } + + public void deletePermissions(String recipient) + { + permissionsDaoComponent.deletePermissions(recipient); + accessCache.clear(); + } + + // + // SUPPORT CLASSES + // + + /** + * Support class to test the permission on a node. + * + * @author Andy Hind + */ + private class NodeTest + { + /* + * The required permission. + */ + PermissionReference required; + + /* + * Granters of the permission + */ + Set granters; + + /* + * The additional permissions required at the node level. + */ + Set nodeRequirements = new HashSet(); + + /* + * The additional permissions required on the parent. + */ + Set parentRequirements = new HashSet(); + + /* + * The permissions required on all children . + */ + Set childrenRequirements = new HashSet(); + + /* + * The type name of the node. + */ + QName typeQName; + + /* + * The aspects set on the node. + */ + Set aspectQNames; + + /* + * Constructor just gets the additional requirements + */ + NodeTest(PermissionReference required, QName typeQName, Set aspectQNames) + { + this.required = required; + this.typeQName = typeQName; + this.aspectQNames = aspectQNames; + + // Set the required node permissions + nodeRequirements = modelDAO.getRequiredPermissions(required, typeQName, aspectQNames, + RequiredPermission.On.NODE); + + parentRequirements = modelDAO.getRequiredPermissions(required, typeQName, aspectQNames, + RequiredPermission.On.PARENT); + + childrenRequirements = modelDAO.getRequiredPermissions(required, typeQName, aspectQNames, + RequiredPermission.On.CHILDREN); + + // Find all the permissions that grant the allowed permission + // All permissions are treated specially. + granters = modelDAO.getGrantingPermissions(required); + granters.add(getAllPermissionReference()); + granters.add(OLD_ALL_PERMISSIONS_REFERENCE); + } + + /** + * External hook point + * + * @param authorisations + * @param nodeRef + * @return + */ + boolean evaluate(Set authorisations, NodeRef nodeRef) + { + Set> denied = new HashSet>(); + return evaluate(authorisations, nodeRef, denied, null); + } + + /** + * Internal hook point for recursion + * + * @param authorisations + * @param nodeRef + * @param denied + * @param recursiveIn + * @return + */ + boolean evaluate(Set authorisations, NodeRef nodeRef, Set> denied, + MutableBoolean recursiveIn) + { + // Do we defer our required test to a parent (yes if not null) + MutableBoolean recursiveOut = null; + + Set> locallyDenied = new HashSet>(); + locallyDenied.addAll(denied); + locallyDenied.addAll(getDenied(nodeRef)); + + // Start out true and "and" all other results + boolean success = true; + + // Check the required permissions but not for sets they rely on + // their underlying permissions + if (required.equals(getPermissionReference(ALL_PERMISSIONS)) || modelDAO.checkPermission(required)) + { + if (parentRequirements.contains(required)) + { + if (checkGlobalPermissions(authorisations) || checkRequired(authorisations, nodeRef, locallyDenied)) + { + // No need to do the recursive test as it has been found + recursiveOut = null; + if (recursiveIn != null) + { + recursiveIn.setValue(true); + } + } + else + { + // Much cheaper to do this as we go then check all the + // stack values for each parent + recursiveOut = new MutableBoolean(false); + } + } + else + { + // We have to do the test as no parent will help us out + success &= hasSinglePermission(authorisations, nodeRef); + } + if (!success) + { + return false; + } + } + + // Check the other permissions required on the node + for (PermissionReference pr : nodeRequirements) + { + // Build a new test + NodeTest nt = new NodeTest(pr, typeQName, aspectQNames); + success &= nt.evaluate(authorisations, nodeRef, locallyDenied, null); + if (!success) + { + return false; + } + } + + // Check the permission required of the parent + + if (success) + { + ChildAssociationRef car = nodeService.getPrimaryParent(nodeRef); + if (car.getParentRef() != null) + { + + NodePermissionEntry nodePermissions = permissionsDaoComponent.getPermissions(car.getChildRef()); + if ((nodePermissions == null) || (nodePermissions.inheritPermissions())) + { + + locallyDenied.addAll(getDenied(car.getParentRef())); + for (PermissionReference pr : parentRequirements) + { + if (pr.equals(required)) + { + // Recursive permission + success &= this.evaluate(authorisations, car.getParentRef(), locallyDenied, + recursiveOut); + if ((recursiveOut != null) && recursiveOut.getValue()) + { + if (recursiveIn != null) + { + recursiveIn.setValue(true); + } + } + } + else + { + NodeTest nt = new NodeTest(pr, typeQName, aspectQNames); + success &= nt.evaluate(authorisations, car.getParentRef(), locallyDenied, null); + } + + if (!success) + { + return false; + } + } + } + } + } + + if ((recursiveOut != null) && (!recursiveOut.getValue())) + { + // The required authentication was not resolved in recursion + return false; + } + + // Check permissions required of children + if (childrenRequirements.size() > 0) + { + List childAssocRefs = nodeService.getChildAssocs(nodeRef); + for (PermissionReference pr : childrenRequirements) + { + for (ChildAssociationRef child : childAssocRefs) + { + success &= (hasPermission(child.getChildRef(), pr) == AccessStatus.ALLOWED); + if (!success) + { + return false; + } + } + } + } + + return success; + } + + public boolean hasSinglePermission(Set authorisations, NodeRef nodeRef) + { + // Check global permission + + if (checkGlobalPermissions(authorisations)) + { + return true; + } + + Set> denied = new HashSet>(); + + // Keep track of permission that are denied + + // Permissions are only evaluated up the primary parent chain + // TODO: Do not ignore non primary permissions + ChildAssociationRef car = nodeService.getPrimaryParent(nodeRef); + // Work up the parent chain evaluating permissions. + while (car != null) + { + // Add any denied permission to the denied list - these can not + // then + // be used to given authentication. + // A -> B -> C + // If B denies all permissions to any - allowing all permissions + // to + // andy at node A has no effect + + denied.addAll(getDenied(car.getChildRef())); + + // If the current node allows the permission we are done + // The test includes any parent or ancestor requirements + if (checkRequired(authorisations, car.getChildRef(), denied)) + { + return true; + } + + // Build the next element of the evaluation chain + if (car.getParentRef() != null) + { + NodePermissionEntry nodePermissions = permissionsDaoComponent.getPermissions(car.getChildRef()); + if ((nodePermissions == null) || (nodePermissions.inheritPermissions())) + { + car = nodeService.getPrimaryParent(car.getParentRef()); + } + else + { + car = null; + } + } + else + { + car = null; + } + + } + + // TODO: Support meta data permissions on the root node? + + return false; + + } + + /** + * Check if we have a global permission + * + * @param authorisations + * @return + */ + private boolean checkGlobalPermissions(Set authorisations) + { + for (PermissionEntry pe : modelDAO.getGlobalPermissionEntries()) + { + if (isGranted(pe, authorisations, null)) + { + return true; + } + } + return false; + } + + /** + * Get the list of permissions denied for this node. + * + * @param nodeRef + * @return + */ + Set> getDenied(NodeRef nodeRef) + { + Set> deniedSet = new HashSet>(); + + // Loop over all denied permissions + NodePermissionEntry nodeEntry = permissionsDaoComponent.getPermissions(nodeRef); + if (nodeEntry != null) + { + for (PermissionEntry pe : nodeEntry.getPermissionEntries()) + { + if (pe.isDenied()) + { + // All the sets that grant this permission must be + // denied + // Note that granters includes the orginal permission + Set granters = modelDAO + .getGrantingPermissions(pe.getPermissionReference()); + for (PermissionReference granter : granters) + { + deniedSet.add(new Pair(pe.getAuthority(), granter)); + } + + // All the things granted by this permission must be + // denied + Set grantees = modelDAO.getGranteePermissions(pe.getPermissionReference()); + for (PermissionReference grantee : grantees) + { + deniedSet.add(new Pair(pe.getAuthority(), grantee)); + } + + // All permission excludes all permissions available for + // the node. + if (pe.getPermissionReference().equals(getAllPermissionReference()) || pe.getPermissionReference().equals(OLD_ALL_PERMISSIONS_REFERENCE)) + { + for (PermissionReference deny : modelDAO.getAllPermissions(nodeRef)) + { + deniedSet.add(new Pair(pe.getAuthority(), deny)); + } + } + } + } + } + return deniedSet; + } + + /** + * Check that a given authentication is available on a node + * + * @param authorisations + * @param nodeRef + * @param denied + * @return + */ + boolean checkRequired(Set authorisations, NodeRef nodeRef, Set> denied) + { + NodePermissionEntry nodeEntry = permissionsDaoComponent.getPermissions(nodeRef); + + // No permissions set - short cut to deny + if (nodeEntry == null) + { + return false; + } + + // Check if each permission allows - the first wins. + // We could have other voting style mechanisms here + for (PermissionEntry pe : nodeEntry.getPermissionEntries()) + { + if (isGranted(pe, authorisations, denied)) + { + return true; + } + } + return false; + } + + /** + * Is a permission granted + * + * @param pe - + * the permissions entry to consider + * @param granters - + * the set of granters + * @param authorisations - + * the set of authorities + * @param denied - + * the set of denied permissions/authority pais + * @return + */ + private boolean isGranted(PermissionEntry pe, Set authorisations, + Set> denied) + { + // If the permission entry denies then we just deny + if (pe.isDenied()) + { + return false; + } + + // The permission is allowed but we deny it as it is in the denied + // set + if (denied != null) + { + Pair specific = new Pair(pe.getAuthority(), + required); + if (denied.contains(specific)) + { + return false; + } + } + + // If the permission has a match in both the authorities and + // granters list it is allowed + // It applies to the current user and it is granted + if (authorisations.contains(pe.getAuthority()) && granters.contains(pe.getPermissionReference())) + { + { + return true; + } + } + + // Default deny + return false; + } + + } + + /** + * Helper class to store a pair of objects which may be null + * + * @author Andy Hind + */ + private static class Pair + { + A a; + + B b; + + Pair(A a, B b) + { + this.a = a; + this.b = b; + } + + A getA() + { + return a; + } + + B getB() + { + return b; + } + + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (!(this instanceof Pair)) + { + return false; + } + Pair other = (Pair) o; + return EqualsHelper.nullSafeEquals(this.getA(), other.getA()) + && EqualsHelper.nullSafeEquals(this.getB(), other.getB()); + } + + @Override + public int hashCode() + { + return (((a == null) ? 0 : a.hashCode()) * 37) + ((b == null) ? 0 : b.hashCode()); + } + + } + + private static class MutableBoolean + { + private boolean value; + + MutableBoolean(boolean value) + { + this.value = value; + } + + void setValue(boolean value) + { + this.value = value; + } + + boolean getValue() + { + return value; + } + } +} diff --git a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java index d03bc09118..8f2acca924 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java @@ -1,1839 +1,1839 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.repo.security.permissions.impl; - -import java.util.HashSet; -import java.util.Set; - -import net.sf.acegisecurity.Authentication; -import net.sf.acegisecurity.GrantedAuthority; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.security.permissions.PermissionEntry; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.security.AccessPermission; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.AuthorityType; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.namespace.QName; - -public class PermissionServiceTest extends AbstractPermissionTest -{ - private SimplePermissionEntry denyAndyAll; - - private SimplePermissionEntry allowAndyAll; - - private SimplePermissionEntry denyAndyRead; - - private SimplePermissionEntry allowAndyRead; - - private SimplePermissionEntry denyAndyReadProperties; - - private SimplePermissionEntry allowAndyReadProperties; - - private SimplePermissionEntry allowAndyReadChildren; - - public PermissionServiceTest() - { - super(); - // TODO Auto-generated constructor stub - } - - public void testAuthenticatedRoleIsPresent() - { - runAs("andy"); - Authentication auth = authenticationComponent.getCurrentAuthentication(); - for (GrantedAuthority authority : auth.getAuthorities()) - { - if (authority.getAuthority().equals(ROLE_AUTHENTICATED)) { return; } - } - fail("Missing role ROLE_AUTHENTICATED "); - } - - @Override - protected void onSetUpInTransaction() throws Exception - { - super.onSetUpInTransaction(); - denyAndyAll = new SimplePermissionEntry(rootNodeRef, permissionService.getAllPermissionReference(), "andy", - AccessStatus.DENIED); - allowAndyAll = new SimplePermissionEntry(rootNodeRef, permissionService.getAllPermissionReference(), "andy", - AccessStatus.ALLOWED); - denyAndyRead = new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), "andy", - AccessStatus.DENIED); - allowAndyRead = new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), "andy", - AccessStatus.ALLOWED); - denyAndyReadProperties = new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.DENIED); - allowAndyReadProperties = new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.ALLOWED); - allowAndyReadChildren = new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ_CHILDREN), - "andy", AccessStatus.ALLOWED); - } - - public void testWeSetConsumerOnRootIsNotSupportedByHasPermisssionAsItIsTheWrongType() - { - runAs("andy"); - assertEquals(0, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.CONSUMER), - "andy", AccessStatus.ALLOWED)); - assertEquals(1, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - assertEquals(permissionService.hasPermission(rootNodeRef, (PermissionService.CONSUMER)), AccessStatus.DENIED); - } - - public void testGetAllSetPermissions() - { - runAs("andy"); - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.DELETE), - "andy", AccessStatus.ALLOWED)); - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.DELETE), - "GROUP_GREEN", AccessStatus.ALLOWED)); - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), - "andy", AccessStatus.ALLOWED)); - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), - "GROUP_RED", AccessStatus.ALLOWED)); - permissionService.setPermission(new SimplePermissionEntry(systemNodeRef, - getPermission(PermissionService.DELETE), "andy", AccessStatus.DENIED)); - permissionService.setPermission(new SimplePermissionEntry(systemNodeRef, - getPermission(PermissionService.DELETE), "GROUP_GREEN", AccessStatus.DENIED)); - - NodeRef current = systemNodeRef; - Set setPermissions = new HashSet(); - while (current != null) - { - Set morePermissions = permissionService.getAllSetPermissions(current); - for (AccessPermission toTest : morePermissions) - { - if (toTest.getAuthorityType() == AuthorityType.GROUP) - { - boolean add = true; - for (AccessPermission existing : setPermissions) - { - if (add - && existing.getAuthority().equals(toTest.getAuthority()) - && existing.getPermission().equals(toTest.getPermission())) - { - add = false; - } - - } - if (add) - { - setPermissions.add(toTest); - } - } - } - if (permissionService.getInheritParentPermissions(current)) - { - current = nodeService.getPrimaryParent(current).getParentRef(); - } - else - { - current = null; - } - } - assertEquals(2, setPermissions.size()); - - } - - public void testPermissionCacheOnMove() - { - runAs("admin"); - - NodeRef n1 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, - QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); - NodeRef n2 = nodeService.createNode(n1, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}two"), - ContentModel.TYPE_FOLDER).getChildRef(); - - permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ), "andy", - AccessStatus.ALLOWED)); - - runAs("andy"); - - assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - - runAs("admin"); - nodeService.moveNode(n2, rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}oneMoved")); - - runAs("andy"); - - assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.DENIED); - } - - public void testSetInheritFalse() - { - runAs("andy"); - permissionService.setInheritParentPermissions(rootNodeRef, false); - assertNotNull(permissionService.getSetPermissions(rootNodeRef)); - assertFalse(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); - assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); - assertEquals(0, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - } - - public void testSetInheritTrue() - { - runAs("andy"); - permissionService.setInheritParentPermissions(rootNodeRef, true); - assertNotNull(permissionService.getSetPermissions(rootNodeRef)); - assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); - assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); - assertEquals(0, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - - permissionService.deletePermissions(permissionService.getSetPermissions(rootNodeRef)); - } - - public void testAlterInherit() - { - runAs("andy"); - testSetInheritFalse(); - testSetInheritTrue(); - testSetInheritFalse(); - testSetInheritTrue(); - - permissionService.deletePermissions(rootNodeRef); - // testUnset(); - } - - public void testSetNodePermissionEntry() - { - runAs("andy"); - Set entries = new HashSet(); - entries.add(new SimplePermissionEntry(rootNodeRef, new SimplePermissionReference(QName.createQName("A", "B"), - "C"), "user-one", AccessStatus.ALLOWED)); - entries.add(new SimplePermissionEntry(rootNodeRef, permissionService.getAllPermissionReference(), "user-two", - AccessStatus.ALLOWED)); - entries.add(new SimplePermissionEntry(rootNodeRef, new SimplePermissionReference(QName.createQName("D", "E"), - "F"), permissionService.getAllAuthorities(), AccessStatus.ALLOWED)); - entries.add(new SimplePermissionEntry(rootNodeRef, permissionService.getAllPermissionReference(), - permissionService.getAllAuthorities(), AccessStatus.DENIED)); - - SimpleNodePermissionEntry entry = new SimpleNodePermissionEntry(rootNodeRef, false, entries); - - permissionService.setPermission(entry); - - assertNotNull(permissionService.getSetPermissions(rootNodeRef)); - assertFalse(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); - assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); - assertEquals(4, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - } - - public void testSetNodePermissionEntry2() - { - Set entries = new HashSet(); - entries.add(new SimplePermissionEntry(rootNodeRef, permissionService.getAllPermissionReference(), - permissionService.getAllAuthorities(), AccessStatus.ALLOWED)); - - SimpleNodePermissionEntry entry = new SimpleNodePermissionEntry(rootNodeRef, false, entries); - - permissionService.setPermission(entry); - - assertNotNull(permissionService.getSetPermissions(rootNodeRef)); - assertFalse(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); - assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); - assertEquals(1, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - } - - public void testAlterNodePermissions() - { - testSetNodePermissionEntry(); - testSetNodePermissionEntry2(); - testSetNodePermissionEntry(); - testSetNodePermissionEntry2(); - } - - public void testDoubleSetAllowDeny() - { - Set permissionEntries = null; - // add-remove andy-all - permissionService.setPermission(rootNodeRef, "andy", permissionService.getAllPermission(), true); - permissionService.setPermission(rootNodeRef, "andy", permissionService.getAllPermission(), false); - permissionService.deletePermission(rootNodeRef, "andy", permissionService.getAllPermission()); - permissionEntries = permissionService.getSetPermissions(rootNodeRef).getPermissionEntries(); - assertEquals(0, permissionEntries.size()); - // add-remove andy-read - permissionService.setPermission(rootNodeRef, "andy", PermissionService.READ, true); - permissionService.setPermission(rootNodeRef, "andy", PermissionService.READ, false); - permissionService.deletePermission(rootNodeRef, "andy", PermissionService.READ); - permissionEntries = permissionService.getSetPermissions(rootNodeRef).getPermissionEntries(); - assertEquals(0, permissionEntries.size()); - } - - public void testSetPermissionEntryElements() - { - // add andy-all (allow) - permissionService.setPermission(rootNodeRef, "andy", permissionService.getAllPermission(), true); - assertNotNull(permissionService.getSetPermissions(rootNodeRef)); - assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); - assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); - assertEquals(1, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - for (PermissionEntry pe : permissionService.getSetPermissions(rootNodeRef).getPermissionEntries()) - { - assertEquals("andy", pe.getAuthority()); - assertTrue(pe.isAllowed()); - assertTrue(pe.getPermissionReference().getQName().equals( - permissionService.getAllPermissionReference().getQName())); - assertTrue(pe.getPermissionReference().getName().equals( - permissionService.getAllPermissionReference().getName())); - assertEquals(rootNodeRef, pe.getNodeRef()); - } - - // add andy-all (allow) - permissionService.setPermission(rootNodeRef, "andy", permissionService.getAllPermission(), true); - assertNotNull(permissionService.getSetPermissions(rootNodeRef)); - assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); - assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); - assertEquals(1, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - - // add other-all (allow) - permissionService.setPermission(rootNodeRef, "other", permissionService.getAllPermission(), true); - assertNotNull(permissionService.getSetPermissions(rootNodeRef)); - assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); - assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); - assertEquals(2, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - - // add andy-all (deny) - permissionService.setPermission(rootNodeRef, "andy", permissionService.getAllPermission(), false); - assertNotNull(permissionService.getSetPermissions(rootNodeRef)); - assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); - assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); - assertEquals(2, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - - // add andy-read (deny) - permissionService.setPermission(rootNodeRef, "andy", PermissionService.READ, false); - assertNotNull(permissionService.getSetPermissions(rootNodeRef)); - assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); - assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); - assertEquals(3, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - - // remove andy-read - permissionService.deletePermission(rootNodeRef, "andy", PermissionService.READ); - assertNotNull(permissionService.getSetPermissions(rootNodeRef)); - assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); - assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); - assertEquals(2, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - - // remove andy-all - permissionService.deletePermission(rootNodeRef, "andy", permissionService.getAllPermission()); - assertNotNull(permissionService.getSetPermissions(rootNodeRef)); - assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); - assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); - assertEquals(1, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - - // remove other-all - permissionService.deletePermission(rootNodeRef, "other", permissionService.getAllPermission()); - assertNotNull(permissionService.getSetPermissions(rootNodeRef)); - assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); - assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); - assertEquals(0, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - } - - public void testSetPermissionEntry() - { - permissionService.setPermission(allowAndyAll); - permissionService.setPermission(rootNodeRef, "andy", permissionService.getAllPermission(), true); - assertNotNull(permissionService.getSetPermissions(rootNodeRef)); - assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); - assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); - assertEquals(1, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - for (PermissionEntry pe : permissionService.getSetPermissions(rootNodeRef).getPermissionEntries()) - { - assertEquals("andy", pe.getAuthority()); - assertTrue(pe.isAllowed()); - assertTrue(pe.getPermissionReference().getQName().equals( - permissionService.getAllPermissionReference().getQName())); - assertTrue(pe.getPermissionReference().getName().equals( - permissionService.getAllPermissionReference().getName())); - assertEquals(rootNodeRef, pe.getNodeRef()); - } - - // Set duplicate - - permissionService.setPermission(allowAndyAll); - assertNotNull(permissionService.getSetPermissions(rootNodeRef)); - assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); - assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); - assertEquals(1, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - - // Set new - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, permissionService - .getAllPermissionReference(), "other", AccessStatus.ALLOWED)); - assertNotNull(permissionService.getSetPermissions(rootNodeRef)); - assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); - assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); - assertEquals(2, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - - // Deny - - permissionService.setPermission(denyAndyAll); - assertNotNull(permissionService.getSetPermissions(rootNodeRef)); - assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); - assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); - assertEquals(2, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - - // new - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, new SimplePermissionReference(QName - .createQName("A", "B"), "C"), "andy", AccessStatus.DENIED)); - assertNotNull(permissionService.getSetPermissions(rootNodeRef)); - assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); - assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); - assertEquals(3, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - - permissionService.deletePermission(new SimplePermissionEntry(rootNodeRef, new SimplePermissionReference(QName - .createQName("A", "B"), "C"), "andy", AccessStatus.DENIED)); - assertNotNull(permissionService.getSetPermissions(rootNodeRef)); - assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); - assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); - assertEquals(2, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - - permissionService.deletePermission(denyAndyAll); - assertNotNull(permissionService.getSetPermissions(rootNodeRef)); - assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); - assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); - assertEquals(1, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - - permissionService.deletePermission(new SimplePermissionEntry(rootNodeRef, permissionService - .getAllPermissionReference(), "other", AccessStatus.ALLOWED)); - assertNotNull(permissionService.getSetPermissions(rootNodeRef)); - assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); - assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); - assertEquals(0, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - - // delete when we know there's nothing do delete - permissionService.deletePermission(allowAndyAll); - assertNotNull(permissionService.getSetPermissions(rootNodeRef)); - assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); - assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); - assertEquals(0, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); - } - - public void testGetSettablePermissionsForType() - { - Set answer = permissionService.getSettablePermissions(QName.createQName("sys", "base", - namespacePrefixResolver)); - assertEquals(36, answer.size()); - - answer = permissionService.getSettablePermissions(QName.createQName("cm", "ownable", namespacePrefixResolver)); - assertEquals(0, answer.size()); - - answer = permissionService.getSettablePermissions(QName.createQName("cm", "content", namespacePrefixResolver)); - assertEquals(5, answer.size()); - - answer = permissionService.getSettablePermissions(QName.createQName("cm", "folder", namespacePrefixResolver)); - assertEquals(5, answer.size()); - - answer = permissionService.getSettablePermissions(QName.createQName("cm", "monkey", namespacePrefixResolver)); - assertEquals(0, answer.size()); - } - - - public void testGetSettablePermissionsForNode() - { - QName ownable = QName.createQName("cm", "ownable", namespacePrefixResolver); - - Set answer = permissionService.getSettablePermissions(rootNodeRef); - assertEquals(42, answer.size()); - - nodeService.addAspect(rootNodeRef, ownable, null); - answer = permissionService.getSettablePermissions(rootNodeRef); - assertEquals(42, answer.size()); - - nodeService.removeAspect(rootNodeRef, ownable); - answer = permissionService.getSettablePermissions(rootNodeRef); - assertEquals(42, answer.size()); - } - - public void testSimplePermissionOnRoot() - { - runAs("andy"); - - assertEquals(42, permissionService.getPermissions(rootNodeRef).size()); - assertEquals(0, countGranted(permissionService.getPermissions(rootNodeRef))); - assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); - - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.ALLOWED)); - assertEquals(1, permissionService.getAllSetPermissions(rootNodeRef).size()); - runAs("andy"); - - assertEquals(42, permissionService.getPermissions(rootNodeRef).size()); - assertEquals(2, countGranted(permissionService.getPermissions(rootNodeRef))); - - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.DENIED)); - assertEquals(1, permissionService.getAllSetPermissions(rootNodeRef).size()); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.ALLOWED)); - assertEquals(1, permissionService.getAllSetPermissions(rootNodeRef).size()); - runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - - permissionService.deletePermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.DENIED)); - assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - - permissionService.deletePermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.ALLOWED)); - assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - } - - private int countGranted(Set permissions) - { - int count = 0; - for (AccessPermission ap : permissions) - { - if (ap.getAccessStatus() == AccessStatus.ALLOWED) - { - count++; - } - } - return count; - } - - public void testGlobalPermissionsForAdmin() - { - runAs("admin"); - NodeRef n1 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, - QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); - - NodeRef n2 = nodeService.createNode(n1, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}two"), - ContentModel.TYPE_CONTENT).getChildRef(); - - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), - "admin", AccessStatus.DENIED)); - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_PROPERTIES), "admin", AccessStatus.DENIED)); - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_CHILDREN), "admin", AccessStatus.DENIED)); - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_CONTENT), "admin", AccessStatus.DENIED)); - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.ALL_PERMISSIONS), "admin", AccessStatus.DENIED)); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - } - - public void testPermissionGroupOnRoot() - { - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); - permissionService.setPermission(allowAndyRead); - runAs("andy"); - - assertEquals(42, permissionService.getPermissions(rootNodeRef).size()); - assertEquals(7, countGranted(permissionService.getPermissions(rootNodeRef))); - assertEquals(1, permissionService.getAllSetPermissions(rootNodeRef).size()); - - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(denyAndyRead); - runAs("andy"); - assertEquals(1, permissionService.getAllSetPermissions(rootNodeRef).size()); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.deletePermission(allowAndyRead); - runAs("andy"); - assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("andy"); - } - - public void testSimplePermissionSimpleInheritance() - { - runAs("admin"); - - NodeRef n1 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, - QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); - - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - - assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); - permissionService.setPermission(allowAndyReadProperties); - runAs("andy"); - assertEquals(1, permissionService.getAllSetPermissions(rootNodeRef).size()); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - // Changed ny not enfocing READ - // assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - // assertFalse(permissionService.hasPermission(n1, - // getPermission(PermissionService.READ_PROPERTIES)) == - // AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - - permissionService.setPermission(allowAndyReadChildren); - assertEquals(2, permissionService.getAllSetPermissions(rootNodeRef).size()); - runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - - permissionService.setPermission(denyAndyReadProperties); - assertEquals(2, permissionService.getAllSetPermissions(rootNodeRef).size()); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - - permissionService.deletePermission(allowAndyReadChildren); - assertEquals(1, permissionService.getAllSetPermissions(rootNodeRef).size()); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - - permissionService.deletePermission(allowAndyReadProperties); - assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - } - - public void testPermissionGroupSimpleInheritance() - { - runAs("admin"); - - NodeRef n1 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, - QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); - - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(allowAndyRead); - runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(denyAndyRead); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.deletePermission(allowAndyRead); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - } - - public void testDenySimplePermisionOnRootNode() - { - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - - permissionService.setPermission(allowAndyReadProperties); - runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - - permissionService.setPermission(denyAndyReadProperties); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - - permissionService.deletePermission(allowAndyReadProperties); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - } - - public void testDenyPermissionOnRootNOde() - { - - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(allowAndyRead); - runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(denyAndyRead); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.deletePermission(allowAndyRead); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - } - - public void testComplexDenyOnRootNode() - { - - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(allowAndyRead); - runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(denyAndyReadProperties); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(allowAndyReadChildren); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(denyAndyRead); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - } - - public void testPerf() throws Exception - { - runAs("admin"); - - // TransactionService transactionService = serviceRegistry.getTransactionService(); - // UserTransaction tx = transactionService.getUserTransaction(); - // tx.begin(); - - NodeRef n1 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, - QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); - NodeRef n2 = nodeService.createNode(n1, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}two"), - ContentModel.TYPE_FOLDER).getChildRef(); - NodeRef n3 = nodeService.createNode(n2, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}three"), - ContentModel.TYPE_FOLDER).getChildRef(); - NodeRef n4 = nodeService.createNode(n3, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}four"), - ContentModel.TYPE_FOLDER).getChildRef(); - NodeRef n5 = nodeService.createNode(n4, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}five"), - ContentModel.TYPE_FOLDER).getChildRef(); - NodeRef n6 = nodeService.createNode(n5, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}six"), - ContentModel.TYPE_FOLDER).getChildRef(); - NodeRef n7 = nodeService.createNode(n6, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}seven"), - ContentModel.TYPE_FOLDER).getChildRef(); - NodeRef n8 = nodeService.createNode(n7, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}eight"), - ContentModel.TYPE_FOLDER).getChildRef(); - NodeRef n9 = nodeService.createNode(n8, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}nine"), - ContentModel.TYPE_FOLDER).getChildRef(); - NodeRef n10 = nodeService.createNode(n9, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}ten"), - ContentModel.TYPE_FOLDER).getChildRef(); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), - "andy", AccessStatus.ALLOWED)); - // permissionService.setPermission(new SimplePermissionEntry(n9, - // getPermission(PermissionService.READ), - // "andy", AccessStatus.ALLOWED)); - // permissionService.setPermission(new SimplePermissionEntry(n10, - // getPermission(PermissionService.READ), - // "andy", AccessStatus.ALLOWED)); - - long start; - long end; - long time = 0; - for (int i = 0; i < 1000; i++) - { - getSession().flush(); - // getSession().clear(); - start = System.nanoTime(); - assertTrue(permissionService.hasPermission(n10, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - end = System.nanoTime(); - time += (end - start); - } - System.out.println("Time is " + (time / 1000000000.0)); - // assertTrue((time / 1000000000.0) < 60.0); - - time = 0; - for (int i = 0; i < 1000; i++) - { - start = System.nanoTime(); - assertTrue(permissionService.hasPermission(n10, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - end = System.nanoTime(); - time += (end - start); - } - System.out.println("Time is " + (time / 1000000000.0)); - // assertTrue((time / 1000000000.0) < 2.0); - - // tx.rollback(); - } - - public void testAllPermissions() - { - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.ALL_PERMISSIONS)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, PermissionServiceImpl.OLD_ALL_PERMISSIONS_REFERENCE) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); - - permissionService.setPermission(allowAndyAll); - assertEquals(1, permissionService.getAllSetPermissions(rootNodeRef).size()); - runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.ALL_PERMISSIONS)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, PermissionServiceImpl.OLD_ALL_PERMISSIONS_REFERENCE) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(denyAndyRead); - runAs("andy"); - assertEquals(2, permissionService.getAllSetPermissions(rootNodeRef).size()); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.ALL_PERMISSIONS)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, PermissionServiceImpl.OLD_ALL_PERMISSIONS_REFERENCE) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(denyAndyAll); - assertEquals(2, permissionService.getAllSetPermissions(rootNodeRef).size()); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.ALL_PERMISSIONS)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, PermissionServiceImpl.OLD_ALL_PERMISSIONS_REFERENCE) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - } - - public void testOldAllPermissions() - { - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.ALL_PERMISSIONS)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, PermissionServiceImpl.OLD_ALL_PERMISSIONS_REFERENCE) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - PermissionServiceImpl.OLD_ALL_PERMISSIONS_REFERENCE, "andy", AccessStatus.ALLOWED)); - assertEquals(1, permissionService.getAllSetPermissions(rootNodeRef).size()); - runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.ALL_PERMISSIONS)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, PermissionServiceImpl.OLD_ALL_PERMISSIONS_REFERENCE) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(denyAndyRead); - runAs("andy"); - assertEquals(2, permissionService.getAllSetPermissions(rootNodeRef).size()); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.ALL_PERMISSIONS)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, PermissionServiceImpl.OLD_ALL_PERMISSIONS_REFERENCE) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(denyAndyAll); - assertEquals(3, permissionService.getAllSetPermissions(rootNodeRef).size()); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.ALL_PERMISSIONS)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, PermissionServiceImpl.OLD_ALL_PERMISSIONS_REFERENCE) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - } - - public void testAuthenticatedAuthority() - { - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), - ROLE_AUTHENTICATED, AccessStatus.ALLOWED)); - runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), - ROLE_AUTHENTICATED, AccessStatus.DENIED)); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.deletePermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ), ROLE_AUTHENTICATED, AccessStatus.ALLOWED)); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - } - - public void testAllAuthorities() - { - - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), - permissionService.getAllAuthorities(), AccessStatus.ALLOWED)); - runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), - permissionService.getAllAuthorities(), AccessStatus.DENIED)); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.deletePermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ), permissionService.getAllAuthorities(), AccessStatus.ALLOWED)); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - } - - public void testAllPermissionsAllAuthorities() - { - - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, permissionService - .getAllPermissionReference(), permissionService.getAllAuthorities(), AccessStatus.ALLOWED)); - runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), - permissionService.getAllAuthorities(), AccessStatus.DENIED)); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, permissionService - .getAllPermissionReference(), permissionService.getAllAuthorities(), AccessStatus.DENIED)); - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - } - - public void testGroupAndUserInteraction() - { - - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), - "andy", AccessStatus.ALLOWED)); - runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), - ROLE_AUTHENTICATED, AccessStatus.ALLOWED)); - runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.DENIED)); - runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_CHILDREN), "andy", AccessStatus.ALLOWED)); - runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), - "andy", AccessStatus.DENIED)); - runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - } - - public void testInheritPermissions() - { - runAs("admin"); - NodeRef n1 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, - QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); - NodeRef n2 = nodeService.createNode(n1, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}two"), - ContentModel.TYPE_FOLDER).getChildRef(); - - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), - "andy", AccessStatus.ALLOWED)); - permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ), "andy", - AccessStatus.ALLOWED)); - - runAs("andy"); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setInheritParentPermissions(n2, false); - - runAs("andy"); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setInheritParentPermissions(n2, true); - - runAs("andy"); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - } - - public void testAncestorRequirementAndInheritance() - { - runAs("admin"); - - NodeRef n1 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, - QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); - NodeRef n2 = nodeService.createNode(n1, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}two"), - ContentModel.TYPE_FOLDER).getChildRef(); - - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_CHILDREN), "andy", AccessStatus.ALLOWED)); - permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ_CHILDREN), - "andy", AccessStatus.ALLOWED)); - permissionService.setPermission(new SimplePermissionEntry(n2, getPermission(PermissionService.READ_PROPERTIES), - "andy", AccessStatus.ALLOWED)); - permissionService.setPermission(new SimplePermissionEntry(n2, getPermission(PermissionService.READ_CONTENT), - "andy", AccessStatus.ALLOWED)); - - runAs("andy"); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ_CHILDREN), - "andy", AccessStatus.DENIED)); - permissionService.setInheritParentPermissions(n2, false); - - runAs("andy"); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setInheritParentPermissions(n2, true); - - runAs("andy"); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - // Changed by removing permission read parents access - // assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - } - - public void testPermissionCase() - { - - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_CHILDREN), "Andy", AccessStatus.ALLOWED)); - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_PROPERTIES), "ANDY", AccessStatus.ALLOWED)); - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_CONTENT), "AnDy", AccessStatus.ALLOWED)); - - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - -// permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, -// getPermission(PermissionService.READ_CHILDREN), "andy", AccessStatus.ALLOWED)); -// permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, -// getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.ALLOWED)); -// permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, -// getPermission(PermissionService.READ_CONTENT), "andy", AccessStatus.ALLOWED)); -// -// -// runAs("andy"); -// assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); -// assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); -// assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); -// assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); -// runAs("lemur"); -// assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); -// assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); -// assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); -// assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - } - - public void testEffectiveComposite() - { - - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_CHILDREN), "andy", AccessStatus.ALLOWED)); - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.ALLOWED)); - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_CONTENT), "andy", AccessStatus.ALLOWED)); - - runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - } - - public void testContentPermissions() - { - runAs("admin"); - - NodeRef n1 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, - QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); - NodeRef n2 = nodeService.createNode(n1, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}two"), - ContentModel.TYPE_CONTENT).getChildRef(); - - runAs("andy"); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_CHILDREN), "andy", AccessStatus.ALLOWED)); - permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ_CHILDREN), - "andy", AccessStatus.ALLOWED)); - permissionService.setPermission(new SimplePermissionEntry(n2, getPermission(PermissionService.READ_CHILDREN), - "andy", AccessStatus.ALLOWED)); - permissionService.setPermission(new SimplePermissionEntry(n2, getPermission(PermissionService.READ_PROPERTIES), - "andy", AccessStatus.ALLOWED)); - - runAs("andy"); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(n2, getPermission(PermissionService.READ_CONTENT), - "andy", AccessStatus.ALLOWED)); - - runAs("andy"); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.deletePermission(new SimplePermissionEntry(n2, - getPermission(PermissionService.READ_CHILDREN), "andy", AccessStatus.ALLOWED)); - permissionService.deletePermission(new SimplePermissionEntry(n2, - getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.ALLOWED)); - permissionService.deletePermission(new SimplePermissionEntry(n2, getPermission(PermissionService.READ_CONTENT), - "andy", AccessStatus.ALLOWED)); - - runAs("andy"); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(n2, getPermission(PermissionService.READ), "andy", - AccessStatus.ALLOWED)); - - runAs("andy"); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - } - - public void testAllPermissionSet() - { - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.FULL_CONTROL), "andy", AccessStatus.ALLOWED)); - - runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.FULL_CONTROL), "andy", AccessStatus.DENIED)); - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), - "andy", AccessStatus.ALLOWED)); - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_CHILDREN), "andy", AccessStatus.ALLOWED)); - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.ALLOWED)); - - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - permissionService.deletePermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.FULL_CONTROL), "andy", AccessStatus.DENIED)); - - runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); - - } - - public void testChildrenRequirements() - { - if (!personService.createMissingPeople()) - { - assertEquals(1, nodeService.getChildAssocs(rootNodeRef).size()); - } - runAs("andy"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_NODE)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_NODE)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), - "andy", AccessStatus.ALLOWED)); - - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.DELETE), - "andy", AccessStatus.ALLOWED)); - - runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_NODE)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_NODE)) == AccessStatus.ALLOWED); - - runAs("andy"); - assertTrue(permissionService.hasPermission(systemNodeRef, getPermission(PermissionService.DELETE_CHILDREN)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(systemNodeRef, getPermission(PermissionService.DELETE_NODE)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(systemNodeRef, getPermission(PermissionService.DELETE)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(systemNodeRef, getPermission(PermissionService.DELETE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(systemNodeRef, getPermission(PermissionService.DELETE_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(systemNodeRef, getPermission(PermissionService.DELETE_NODE)) == AccessStatus.ALLOWED); - - permissionService.setPermission(new SimplePermissionEntry(systemNodeRef, - getPermission(PermissionService.DELETE), "andy", AccessStatus.DENIED)); - - runAs("andy"); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_CHILDREN)) == AccessStatus.ALLOWED); - // The following are now true as we have no cascade delete check - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_NODE)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE)) == AccessStatus.ALLOWED); - assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); - runAs("lemur"); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_CHILDREN)) == AccessStatus.ALLOWED); - assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_NODE)) == AccessStatus.ALLOWED); - - } - - public void testClearPermission() - { - assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), - "andy", AccessStatus.ALLOWED)); - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_CHILDREN), "andy", AccessStatus.ALLOWED)); - assertEquals(2, permissionService.getAllSetPermissions(rootNodeRef).size()); - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), - "lemur", AccessStatus.ALLOWED)); - permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, - getPermission(PermissionService.READ_CHILDREN), "lemur", AccessStatus.ALLOWED)); - assertEquals(4, permissionService.getAllSetPermissions(rootNodeRef).size()); - - permissionService.clearPermission(rootNodeRef, "andy"); - assertEquals(2, permissionService.getAllSetPermissions(rootNodeRef).size()); - permissionService.clearPermission(rootNodeRef, "lemur"); - assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); - - } - - // TODO: Test permissions on missing nodes - -} +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.security.permissions.impl; + +import java.util.HashSet; +import java.util.Set; + +import net.sf.acegisecurity.Authentication; +import net.sf.acegisecurity.GrantedAuthority; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.permissions.PermissionEntry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.AccessPermission; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.QName; + +public class PermissionServiceTest extends AbstractPermissionTest +{ + private SimplePermissionEntry denyAndyAll; + + private SimplePermissionEntry allowAndyAll; + + private SimplePermissionEntry denyAndyRead; + + private SimplePermissionEntry allowAndyRead; + + private SimplePermissionEntry denyAndyReadProperties; + + private SimplePermissionEntry allowAndyReadProperties; + + private SimplePermissionEntry allowAndyReadChildren; + + public PermissionServiceTest() + { + super(); + // TODO Auto-generated constructor stub + } + + public void testAuthenticatedRoleIsPresent() + { + runAs("andy"); + Authentication auth = authenticationComponent.getCurrentAuthentication(); + for (GrantedAuthority authority : auth.getAuthorities()) + { + if (authority.getAuthority().equals(ROLE_AUTHENTICATED)) { return; } + } + fail("Missing role ROLE_AUTHENTICATED "); + } + + @Override + protected void onSetUpInTransaction() throws Exception + { + super.onSetUpInTransaction(); + denyAndyAll = new SimplePermissionEntry(rootNodeRef, permissionService.getAllPermissionReference(), "andy", + AccessStatus.DENIED); + allowAndyAll = new SimplePermissionEntry(rootNodeRef, permissionService.getAllPermissionReference(), "andy", + AccessStatus.ALLOWED); + denyAndyRead = new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), "andy", + AccessStatus.DENIED); + allowAndyRead = new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), "andy", + AccessStatus.ALLOWED); + denyAndyReadProperties = new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.DENIED); + allowAndyReadProperties = new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.ALLOWED); + allowAndyReadChildren = new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ_CHILDREN), + "andy", AccessStatus.ALLOWED); + } + + public void testWeSetConsumerOnRootIsNotSupportedByHasPermisssionAsItIsTheWrongType() + { + runAs("andy"); + assertEquals(0, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.CONSUMER), + "andy", AccessStatus.ALLOWED)); + assertEquals(1, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + assertEquals(permissionService.hasPermission(rootNodeRef, (PermissionService.CONSUMER)), AccessStatus.DENIED); + } + + public void testGetAllSetPermissions() + { + runAs("andy"); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.DELETE), + "andy", AccessStatus.ALLOWED)); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.DELETE), + "GROUP_GREEN", AccessStatus.ALLOWED)); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), + "andy", AccessStatus.ALLOWED)); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), + "GROUP_RED", AccessStatus.ALLOWED)); + permissionService.setPermission(new SimplePermissionEntry(systemNodeRef, + getPermission(PermissionService.DELETE), "andy", AccessStatus.DENIED)); + permissionService.setPermission(new SimplePermissionEntry(systemNodeRef, + getPermission(PermissionService.DELETE), "GROUP_GREEN", AccessStatus.DENIED)); + + NodeRef current = systemNodeRef; + Set setPermissions = new HashSet(); + while (current != null) + { + Set morePermissions = permissionService.getAllSetPermissions(current); + for (AccessPermission toTest : morePermissions) + { + if (toTest.getAuthorityType() == AuthorityType.GROUP) + { + boolean add = true; + for (AccessPermission existing : setPermissions) + { + if (add + && existing.getAuthority().equals(toTest.getAuthority()) + && existing.getPermission().equals(toTest.getPermission())) + { + add = false; + } + + } + if (add) + { + setPermissions.add(toTest); + } + } + } + if (permissionService.getInheritParentPermissions(current)) + { + current = nodeService.getPrimaryParent(current).getParentRef(); + } + else + { + current = null; + } + } + assertEquals(2, setPermissions.size()); + + } + + public void testPermissionCacheOnMove() + { + runAs("admin"); + + NodeRef n1 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, + QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); + NodeRef n2 = nodeService.createNode(n1, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}two"), + ContentModel.TYPE_FOLDER).getChildRef(); + + permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ), "andy", + AccessStatus.ALLOWED)); + + runAs("andy"); + + assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + + runAs("admin"); + nodeService.moveNode(n2, rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}oneMoved")); + + runAs("andy"); + + assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.DENIED); + } + + public void testSetInheritFalse() + { + runAs("andy"); + permissionService.setInheritParentPermissions(rootNodeRef, false); + assertNotNull(permissionService.getSetPermissions(rootNodeRef)); + assertFalse(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); + assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); + assertEquals(0, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + } + + public void testSetInheritTrue() + { + runAs("andy"); + permissionService.setInheritParentPermissions(rootNodeRef, true); + assertNotNull(permissionService.getSetPermissions(rootNodeRef)); + assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); + assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); + assertEquals(0, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + + permissionService.deletePermissions(permissionService.getSetPermissions(rootNodeRef)); + } + + public void testAlterInherit() + { + runAs("andy"); + testSetInheritFalse(); + testSetInheritTrue(); + testSetInheritFalse(); + testSetInheritTrue(); + + permissionService.deletePermissions(rootNodeRef); + // testUnset(); + } + + public void testSetNodePermissionEntry() + { + runAs("andy"); + Set entries = new HashSet(); + entries.add(new SimplePermissionEntry(rootNodeRef, new SimplePermissionReference(QName.createQName("A", "B"), + "C"), "user-one", AccessStatus.ALLOWED)); + entries.add(new SimplePermissionEntry(rootNodeRef, permissionService.getAllPermissionReference(), "user-two", + AccessStatus.ALLOWED)); + entries.add(new SimplePermissionEntry(rootNodeRef, new SimplePermissionReference(QName.createQName("D", "E"), + "F"), permissionService.getAllAuthorities(), AccessStatus.ALLOWED)); + entries.add(new SimplePermissionEntry(rootNodeRef, permissionService.getAllPermissionReference(), + permissionService.getAllAuthorities(), AccessStatus.DENIED)); + + SimpleNodePermissionEntry entry = new SimpleNodePermissionEntry(rootNodeRef, false, entries); + + permissionService.setPermission(entry); + + assertNotNull(permissionService.getSetPermissions(rootNodeRef)); + assertFalse(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); + assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); + assertEquals(4, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + } + + public void testSetNodePermissionEntry2() + { + Set entries = new HashSet(); + entries.add(new SimplePermissionEntry(rootNodeRef, permissionService.getAllPermissionReference(), + permissionService.getAllAuthorities(), AccessStatus.ALLOWED)); + + SimpleNodePermissionEntry entry = new SimpleNodePermissionEntry(rootNodeRef, false, entries); + + permissionService.setPermission(entry); + + assertNotNull(permissionService.getSetPermissions(rootNodeRef)); + assertFalse(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); + assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); + assertEquals(1, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + } + + public void testAlterNodePermissions() + { + testSetNodePermissionEntry(); + testSetNodePermissionEntry2(); + testSetNodePermissionEntry(); + testSetNodePermissionEntry2(); + } + + public void testDoubleSetAllowDeny() + { + Set permissionEntries = null; + // add-remove andy-all + permissionService.setPermission(rootNodeRef, "andy", permissionService.getAllPermission(), true); + permissionService.setPermission(rootNodeRef, "andy", permissionService.getAllPermission(), false); + permissionService.deletePermission(rootNodeRef, "andy", permissionService.getAllPermission()); + permissionEntries = permissionService.getSetPermissions(rootNodeRef).getPermissionEntries(); + assertEquals(0, permissionEntries.size()); + // add-remove andy-read + permissionService.setPermission(rootNodeRef, "andy", PermissionService.READ, true); + permissionService.setPermission(rootNodeRef, "andy", PermissionService.READ, false); + permissionService.deletePermission(rootNodeRef, "andy", PermissionService.READ); + permissionEntries = permissionService.getSetPermissions(rootNodeRef).getPermissionEntries(); + assertEquals(0, permissionEntries.size()); + } + + public void testSetPermissionEntryElements() + { + // add andy-all (allow) + permissionService.setPermission(rootNodeRef, "andy", permissionService.getAllPermission(), true); + assertNotNull(permissionService.getSetPermissions(rootNodeRef)); + assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); + assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); + assertEquals(1, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + for (PermissionEntry pe : permissionService.getSetPermissions(rootNodeRef).getPermissionEntries()) + { + assertEquals("andy", pe.getAuthority()); + assertTrue(pe.isAllowed()); + assertTrue(pe.getPermissionReference().getQName().equals( + permissionService.getAllPermissionReference().getQName())); + assertTrue(pe.getPermissionReference().getName().equals( + permissionService.getAllPermissionReference().getName())); + assertEquals(rootNodeRef, pe.getNodeRef()); + } + + // add andy-all (allow) + permissionService.setPermission(rootNodeRef, "andy", permissionService.getAllPermission(), true); + assertNotNull(permissionService.getSetPermissions(rootNodeRef)); + assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); + assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); + assertEquals(1, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + + // add other-all (allow) + permissionService.setPermission(rootNodeRef, "other", permissionService.getAllPermission(), true); + assertNotNull(permissionService.getSetPermissions(rootNodeRef)); + assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); + assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); + assertEquals(2, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + + // add andy-all (deny) + permissionService.setPermission(rootNodeRef, "andy", permissionService.getAllPermission(), false); + assertNotNull(permissionService.getSetPermissions(rootNodeRef)); + assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); + assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); + assertEquals(2, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + + // add andy-read (deny) + permissionService.setPermission(rootNodeRef, "andy", PermissionService.READ, false); + assertNotNull(permissionService.getSetPermissions(rootNodeRef)); + assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); + assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); + assertEquals(3, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + + // remove andy-read + permissionService.deletePermission(rootNodeRef, "andy", PermissionService.READ); + assertNotNull(permissionService.getSetPermissions(rootNodeRef)); + assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); + assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); + assertEquals(2, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + + // remove andy-all + permissionService.deletePermission(rootNodeRef, "andy", permissionService.getAllPermission()); + assertNotNull(permissionService.getSetPermissions(rootNodeRef)); + assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); + assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); + assertEquals(1, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + + // remove other-all + permissionService.deletePermission(rootNodeRef, "other", permissionService.getAllPermission()); + assertNotNull(permissionService.getSetPermissions(rootNodeRef)); + assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); + assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); + assertEquals(0, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + } + + public void testSetPermissionEntry() + { + permissionService.setPermission(allowAndyAll); + permissionService.setPermission(rootNodeRef, "andy", permissionService.getAllPermission(), true); + assertNotNull(permissionService.getSetPermissions(rootNodeRef)); + assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); + assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); + assertEquals(1, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + for (PermissionEntry pe : permissionService.getSetPermissions(rootNodeRef).getPermissionEntries()) + { + assertEquals("andy", pe.getAuthority()); + assertTrue(pe.isAllowed()); + assertTrue(pe.getPermissionReference().getQName().equals( + permissionService.getAllPermissionReference().getQName())); + assertTrue(pe.getPermissionReference().getName().equals( + permissionService.getAllPermissionReference().getName())); + assertEquals(rootNodeRef, pe.getNodeRef()); + } + + // Set duplicate + + permissionService.setPermission(allowAndyAll); + assertNotNull(permissionService.getSetPermissions(rootNodeRef)); + assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); + assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); + assertEquals(1, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + + // Set new + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, permissionService + .getAllPermissionReference(), "other", AccessStatus.ALLOWED)); + assertNotNull(permissionService.getSetPermissions(rootNodeRef)); + assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); + assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); + assertEquals(2, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + + // Deny + + permissionService.setPermission(denyAndyAll); + assertNotNull(permissionService.getSetPermissions(rootNodeRef)); + assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); + assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); + assertEquals(2, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + + // new + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, new SimplePermissionReference(QName + .createQName("A", "B"), "C"), "andy", AccessStatus.DENIED)); + assertNotNull(permissionService.getSetPermissions(rootNodeRef)); + assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); + assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); + assertEquals(3, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + + permissionService.deletePermission(new SimplePermissionEntry(rootNodeRef, new SimplePermissionReference(QName + .createQName("A", "B"), "C"), "andy", AccessStatus.DENIED)); + assertNotNull(permissionService.getSetPermissions(rootNodeRef)); + assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); + assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); + assertEquals(2, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + + permissionService.deletePermission(denyAndyAll); + assertNotNull(permissionService.getSetPermissions(rootNodeRef)); + assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); + assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); + assertEquals(1, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + + permissionService.deletePermission(new SimplePermissionEntry(rootNodeRef, permissionService + .getAllPermissionReference(), "other", AccessStatus.ALLOWED)); + assertNotNull(permissionService.getSetPermissions(rootNodeRef)); + assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); + assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); + assertEquals(0, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + + // delete when we know there's nothing do delete + permissionService.deletePermission(allowAndyAll); + assertNotNull(permissionService.getSetPermissions(rootNodeRef)); + assertTrue(permissionService.getSetPermissions(rootNodeRef).inheritPermissions()); + assertEquals(rootNodeRef, permissionService.getSetPermissions(rootNodeRef).getNodeRef()); + assertEquals(0, permissionService.getSetPermissions(rootNodeRef).getPermissionEntries().size()); + } + + public void testGetSettablePermissionsForType() + { + Set answer = permissionService.getSettablePermissions(QName.createQName("sys", "base", + namespacePrefixResolver)); + assertEquals(36, answer.size()); + + answer = permissionService.getSettablePermissions(QName.createQName("cm", "ownable", namespacePrefixResolver)); + assertEquals(0, answer.size()); + + answer = permissionService.getSettablePermissions(QName.createQName("cm", "content", namespacePrefixResolver)); + assertEquals(5, answer.size()); + + answer = permissionService.getSettablePermissions(QName.createQName("cm", "folder", namespacePrefixResolver)); + assertEquals(5, answer.size()); + + answer = permissionService.getSettablePermissions(QName.createQName("cm", "monkey", namespacePrefixResolver)); + assertEquals(0, answer.size()); + } + + + public void testGetSettablePermissionsForNode() + { + QName ownable = QName.createQName("cm", "ownable", namespacePrefixResolver); + + Set answer = permissionService.getSettablePermissions(rootNodeRef); + assertEquals(42, answer.size()); + + nodeService.addAspect(rootNodeRef, ownable, null); + answer = permissionService.getSettablePermissions(rootNodeRef); + assertEquals(42, answer.size()); + + nodeService.removeAspect(rootNodeRef, ownable); + answer = permissionService.getSettablePermissions(rootNodeRef); + assertEquals(42, answer.size()); + } + + public void testSimplePermissionOnRoot() + { + runAs("andy"); + + assertEquals(42, permissionService.getPermissions(rootNodeRef).size()); + assertEquals(0, countGranted(permissionService.getPermissions(rootNodeRef))); + assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); + + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.ALLOWED)); + assertEquals(1, permissionService.getAllSetPermissions(rootNodeRef).size()); + runAs("andy"); + + assertEquals(42, permissionService.getPermissions(rootNodeRef).size()); + assertEquals(2, countGranted(permissionService.getPermissions(rootNodeRef))); + + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.DENIED)); + assertEquals(1, permissionService.getAllSetPermissions(rootNodeRef).size()); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.ALLOWED)); + assertEquals(1, permissionService.getAllSetPermissions(rootNodeRef).size()); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + + permissionService.deletePermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.DENIED)); + assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + + permissionService.deletePermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.ALLOWED)); + assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + } + + private int countGranted(Set permissions) + { + int count = 0; + for (AccessPermission ap : permissions) + { + if (ap.getAccessStatus() == AccessStatus.ALLOWED) + { + count++; + } + } + return count; + } + + public void testGlobalPermissionsForAdmin() + { + runAs("admin"); + NodeRef n1 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, + QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); + + NodeRef n2 = nodeService.createNode(n1, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}two"), + ContentModel.TYPE_CONTENT).getChildRef(); + + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), + "admin", AccessStatus.DENIED)); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_PROPERTIES), "admin", AccessStatus.DENIED)); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_CHILDREN), "admin", AccessStatus.DENIED)); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_CONTENT), "admin", AccessStatus.DENIED)); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.ALL_PERMISSIONS), "admin", AccessStatus.DENIED)); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + } + + public void testPermissionGroupOnRoot() + { + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); + permissionService.setPermission(allowAndyRead); + runAs("andy"); + + assertEquals(42, permissionService.getPermissions(rootNodeRef).size()); + assertEquals(7, countGranted(permissionService.getPermissions(rootNodeRef))); + assertEquals(1, permissionService.getAllSetPermissions(rootNodeRef).size()); + + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(denyAndyRead); + runAs("andy"); + assertEquals(1, permissionService.getAllSetPermissions(rootNodeRef).size()); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.deletePermission(allowAndyRead); + runAs("andy"); + assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("andy"); + } + + public void testSimplePermissionSimpleInheritance() + { + runAs("admin"); + + NodeRef n1 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, + QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); + + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + + assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); + permissionService.setPermission(allowAndyReadProperties); + runAs("andy"); + assertEquals(1, permissionService.getAllSetPermissions(rootNodeRef).size()); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + // Changed ny not enfocing READ + // assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + // assertFalse(permissionService.hasPermission(n1, + // getPermission(PermissionService.READ_PROPERTIES)) == + // AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + + permissionService.setPermission(allowAndyReadChildren); + assertEquals(2, permissionService.getAllSetPermissions(rootNodeRef).size()); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + + permissionService.setPermission(denyAndyReadProperties); + assertEquals(2, permissionService.getAllSetPermissions(rootNodeRef).size()); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + + permissionService.deletePermission(allowAndyReadChildren); + assertEquals(1, permissionService.getAllSetPermissions(rootNodeRef).size()); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + + permissionService.deletePermission(allowAndyReadProperties); + assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + } + + public void testPermissionGroupSimpleInheritance() + { + runAs("admin"); + + NodeRef n1 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, + QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); + + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(allowAndyRead); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(denyAndyRead); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.deletePermission(allowAndyRead); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n1, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + } + + public void testDenySimplePermisionOnRootNode() + { + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + + permissionService.setPermission(allowAndyReadProperties); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + + permissionService.setPermission(denyAndyReadProperties); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + + permissionService.deletePermission(allowAndyReadProperties); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + } + + public void testDenyPermissionOnRootNOde() + { + + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(allowAndyRead); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(denyAndyRead); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.deletePermission(allowAndyRead); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + } + + public void testComplexDenyOnRootNode() + { + + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(allowAndyRead); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(denyAndyReadProperties); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(allowAndyReadChildren); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(denyAndyRead); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + } + + public void testPerf() throws Exception + { + runAs("admin"); + + // TransactionService transactionService = serviceRegistry.getTransactionService(); + // UserTransaction tx = transactionService.getUserTransaction(); + // tx.begin(); + + NodeRef n1 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, + QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); + NodeRef n2 = nodeService.createNode(n1, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}two"), + ContentModel.TYPE_FOLDER).getChildRef(); + NodeRef n3 = nodeService.createNode(n2, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}three"), + ContentModel.TYPE_FOLDER).getChildRef(); + NodeRef n4 = nodeService.createNode(n3, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}four"), + ContentModel.TYPE_FOLDER).getChildRef(); + NodeRef n5 = nodeService.createNode(n4, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}five"), + ContentModel.TYPE_FOLDER).getChildRef(); + NodeRef n6 = nodeService.createNode(n5, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}six"), + ContentModel.TYPE_FOLDER).getChildRef(); + NodeRef n7 = nodeService.createNode(n6, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}seven"), + ContentModel.TYPE_FOLDER).getChildRef(); + NodeRef n8 = nodeService.createNode(n7, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}eight"), + ContentModel.TYPE_FOLDER).getChildRef(); + NodeRef n9 = nodeService.createNode(n8, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}nine"), + ContentModel.TYPE_FOLDER).getChildRef(); + NodeRef n10 = nodeService.createNode(n9, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}ten"), + ContentModel.TYPE_FOLDER).getChildRef(); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), + "andy", AccessStatus.ALLOWED)); + // permissionService.setPermission(new SimplePermissionEntry(n9, + // getPermission(PermissionService.READ), + // "andy", AccessStatus.ALLOWED)); + // permissionService.setPermission(new SimplePermissionEntry(n10, + // getPermission(PermissionService.READ), + // "andy", AccessStatus.ALLOWED)); + + long start; + long end; + long time = 0; + for (int i = 0; i < 1000; i++) + { + getSession().flush(); + // getSession().clear(); + start = System.nanoTime(); + assertTrue(permissionService.hasPermission(n10, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + end = System.nanoTime(); + time += (end - start); + } + System.out.println("Time is " + (time / 1000000000.0)); + // assertTrue((time / 1000000000.0) < 60.0); + + time = 0; + for (int i = 0; i < 1000; i++) + { + start = System.nanoTime(); + assertTrue(permissionService.hasPermission(n10, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + end = System.nanoTime(); + time += (end - start); + } + System.out.println("Time is " + (time / 1000000000.0)); + // assertTrue((time / 1000000000.0) < 2.0); + + // tx.rollback(); + } + + public void testAllPermissions() + { + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.ALL_PERMISSIONS)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, PermissionServiceImpl.OLD_ALL_PERMISSIONS_REFERENCE) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); + + permissionService.setPermission(allowAndyAll); + assertEquals(1, permissionService.getAllSetPermissions(rootNodeRef).size()); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.ALL_PERMISSIONS)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, PermissionServiceImpl.OLD_ALL_PERMISSIONS_REFERENCE) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(denyAndyRead); + runAs("andy"); + assertEquals(2, permissionService.getAllSetPermissions(rootNodeRef).size()); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.ALL_PERMISSIONS)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, PermissionServiceImpl.OLD_ALL_PERMISSIONS_REFERENCE) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(denyAndyAll); + assertEquals(2, permissionService.getAllSetPermissions(rootNodeRef).size()); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.ALL_PERMISSIONS)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, PermissionServiceImpl.OLD_ALL_PERMISSIONS_REFERENCE) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + } + + public void testOldAllPermissions() + { + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.ALL_PERMISSIONS)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, PermissionServiceImpl.OLD_ALL_PERMISSIONS_REFERENCE) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + PermissionServiceImpl.OLD_ALL_PERMISSIONS_REFERENCE, "andy", AccessStatus.ALLOWED)); + assertEquals(1, permissionService.getAllSetPermissions(rootNodeRef).size()); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.ALL_PERMISSIONS)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, PermissionServiceImpl.OLD_ALL_PERMISSIONS_REFERENCE) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(denyAndyRead); + runAs("andy"); + assertEquals(2, permissionService.getAllSetPermissions(rootNodeRef).size()); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.ALL_PERMISSIONS)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, PermissionServiceImpl.OLD_ALL_PERMISSIONS_REFERENCE) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(denyAndyAll); + assertEquals(3, permissionService.getAllSetPermissions(rootNodeRef).size()); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.ALL_PERMISSIONS)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, PermissionServiceImpl.OLD_ALL_PERMISSIONS_REFERENCE) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + } + + public void testAuthenticatedAuthority() + { + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), + ROLE_AUTHENTICATED, AccessStatus.ALLOWED)); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), + ROLE_AUTHENTICATED, AccessStatus.DENIED)); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.deletePermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ), ROLE_AUTHENTICATED, AccessStatus.ALLOWED)); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + } + + public void testAllAuthorities() + { + + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), + permissionService.getAllAuthorities(), AccessStatus.ALLOWED)); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), + permissionService.getAllAuthorities(), AccessStatus.DENIED)); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.deletePermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ), permissionService.getAllAuthorities(), AccessStatus.ALLOWED)); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + } + + public void testAllPermissionsAllAuthorities() + { + + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, permissionService + .getAllPermissionReference(), permissionService.getAllAuthorities(), AccessStatus.ALLOWED)); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), + permissionService.getAllAuthorities(), AccessStatus.DENIED)); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, permissionService + .getAllPermissionReference(), permissionService.getAllAuthorities(), AccessStatus.DENIED)); + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + } + + public void testGroupAndUserInteraction() + { + + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), + "andy", AccessStatus.ALLOWED)); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), + ROLE_AUTHENTICATED, AccessStatus.ALLOWED)); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.DENIED)); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_CHILDREN), "andy", AccessStatus.ALLOWED)); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), + "andy", AccessStatus.DENIED)); + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + } + + public void testInheritPermissions() + { + runAs("admin"); + NodeRef n1 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, + QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); + NodeRef n2 = nodeService.createNode(n1, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}two"), + ContentModel.TYPE_FOLDER).getChildRef(); + + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), + "andy", AccessStatus.ALLOWED)); + permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ), "andy", + AccessStatus.ALLOWED)); + + runAs("andy"); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setInheritParentPermissions(n2, false); + + runAs("andy"); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setInheritParentPermissions(n2, true); + + runAs("andy"); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + } + + public void testAncestorRequirementAndInheritance() + { + runAs("admin"); + + NodeRef n1 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, + QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); + NodeRef n2 = nodeService.createNode(n1, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}two"), + ContentModel.TYPE_FOLDER).getChildRef(); + + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_CHILDREN), "andy", AccessStatus.ALLOWED)); + permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ_CHILDREN), + "andy", AccessStatus.ALLOWED)); + permissionService.setPermission(new SimplePermissionEntry(n2, getPermission(PermissionService.READ_PROPERTIES), + "andy", AccessStatus.ALLOWED)); + permissionService.setPermission(new SimplePermissionEntry(n2, getPermission(PermissionService.READ_CONTENT), + "andy", AccessStatus.ALLOWED)); + + runAs("andy"); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ_CHILDREN), + "andy", AccessStatus.DENIED)); + permissionService.setInheritParentPermissions(n2, false); + + runAs("andy"); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setInheritParentPermissions(n2, true); + + runAs("andy"); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + // Changed by removing permission read parents access + // assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + } + + public void testPermissionCase() + { + + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_CHILDREN), "Andy", AccessStatus.ALLOWED)); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_PROPERTIES), "ANDY", AccessStatus.ALLOWED)); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_CONTENT), "AnDy", AccessStatus.ALLOWED)); + + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + +// permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, +// getPermission(PermissionService.READ_CHILDREN), "andy", AccessStatus.ALLOWED)); +// permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, +// getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.ALLOWED)); +// permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, +// getPermission(PermissionService.READ_CONTENT), "andy", AccessStatus.ALLOWED)); +// +// +// runAs("andy"); +// assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); +// assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); +// assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); +// assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); +// runAs("lemur"); +// assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); +// assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); +// assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); +// assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + } + + public void testEffectiveComposite() + { + + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_CHILDREN), "andy", AccessStatus.ALLOWED)); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.ALLOWED)); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_CONTENT), "andy", AccessStatus.ALLOWED)); + + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + } + + public void testContentPermissions() + { + runAs("admin"); + + NodeRef n1 = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, + QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); + NodeRef n2 = nodeService.createNode(n1, ContentModel.ASSOC_CONTAINS, QName.createQName("{namespace}two"), + ContentModel.TYPE_CONTENT).getChildRef(); + + runAs("andy"); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_CHILDREN), "andy", AccessStatus.ALLOWED)); + permissionService.setPermission(new SimplePermissionEntry(n1, getPermission(PermissionService.READ_CHILDREN), + "andy", AccessStatus.ALLOWED)); + permissionService.setPermission(new SimplePermissionEntry(n2, getPermission(PermissionService.READ_CHILDREN), + "andy", AccessStatus.ALLOWED)); + permissionService.setPermission(new SimplePermissionEntry(n2, getPermission(PermissionService.READ_PROPERTIES), + "andy", AccessStatus.ALLOWED)); + + runAs("andy"); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(n2, getPermission(PermissionService.READ_CONTENT), + "andy", AccessStatus.ALLOWED)); + + runAs("andy"); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.deletePermission(new SimplePermissionEntry(n2, + getPermission(PermissionService.READ_CHILDREN), "andy", AccessStatus.ALLOWED)); + permissionService.deletePermission(new SimplePermissionEntry(n2, + getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.ALLOWED)); + permissionService.deletePermission(new SimplePermissionEntry(n2, getPermission(PermissionService.READ_CONTENT), + "andy", AccessStatus.ALLOWED)); + + runAs("andy"); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(n2, getPermission(PermissionService.READ), "andy", + AccessStatus.ALLOWED)); + + runAs("andy"); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(n2, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + } + + public void testAllPermissionSet() + { + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.FULL_CONTROL), "andy", AccessStatus.ALLOWED)); + + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.FULL_CONTROL), "andy", AccessStatus.DENIED)); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), + "andy", AccessStatus.ALLOWED)); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_CHILDREN), "andy", AccessStatus.ALLOWED)); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_PROPERTIES), "andy", AccessStatus.ALLOWED)); + + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + permissionService.deletePermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.FULL_CONTROL), "andy", AccessStatus.DENIED)); + + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_PROPERTIES)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CONTENT)) == AccessStatus.ALLOWED); + + } + + public void testChildrenRequirements() + { + if (!personService.createMissingPeople()) + { + assertEquals(1, nodeService.getChildAssocs(rootNodeRef).size()); + } + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_NODE)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_NODE)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), + "andy", AccessStatus.ALLOWED)); + + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.DELETE), + "andy", AccessStatus.ALLOWED)); + + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_NODE)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_NODE)) == AccessStatus.ALLOWED); + + runAs("andy"); + assertTrue(permissionService.hasPermission(systemNodeRef, getPermission(PermissionService.DELETE_CHILDREN)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(systemNodeRef, getPermission(PermissionService.DELETE_NODE)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(systemNodeRef, getPermission(PermissionService.DELETE)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(systemNodeRef, getPermission(PermissionService.DELETE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(systemNodeRef, getPermission(PermissionService.DELETE_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(systemNodeRef, getPermission(PermissionService.DELETE_NODE)) == AccessStatus.ALLOWED); + + permissionService.setPermission(new SimplePermissionEntry(systemNodeRef, + getPermission(PermissionService.DELETE), "andy", AccessStatus.DENIED)); + + runAs("andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_CHILDREN)) == AccessStatus.ALLOWED); + // The following are now true as we have no cascade delete check + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_NODE)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE)) == AccessStatus.ALLOWED); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ_CHILDREN)) == AccessStatus.ALLOWED); + runAs("lemur"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_CHILDREN)) == AccessStatus.ALLOWED); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.DELETE_NODE)) == AccessStatus.ALLOWED); + + } + + public void testClearPermission() + { + assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), + "andy", AccessStatus.ALLOWED)); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_CHILDREN), "andy", AccessStatus.ALLOWED)); + assertEquals(2, permissionService.getAllSetPermissions(rootNodeRef).size()); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), + "lemur", AccessStatus.ALLOWED)); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, + getPermission(PermissionService.READ_CHILDREN), "lemur", AccessStatus.ALLOWED)); + assertEquals(4, permissionService.getAllSetPermissions(rootNodeRef).size()); + + permissionService.clearPermission(rootNodeRef, "andy"); + assertEquals(2, permissionService.getAllSetPermissions(rootNodeRef).size()); + permissionService.clearPermission(rootNodeRef, "lemur"); + assertEquals(0, permissionService.getAllSetPermissions(rootNodeRef).size()); + + } + + // TODO: Test permissions on missing nodes + +} diff --git a/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryVoter.java b/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryVoter.java index fef50dd7f4..149bfcc17c 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryVoter.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryVoter.java @@ -44,7 +44,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; /** - * * @author andyh */ @@ -165,14 +164,11 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean } /** - * This implementation supports only MethodSecurityInterceptor, - * because it queries the presented MethodInvocation. + * This implementation supports only MethodSecurityInterceptor, because it queries the presented MethodInvocation. * * @param clazz * the secure object - * - * @return true if the secure object is - * MethodInvocation, false otherwise + * @return true if the secure object is MethodInvocation, false otherwise */ public boolean supports(Class clazz) { @@ -253,7 +249,15 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean testNodeRef = (NodeRef) invocation.getArguments()[cad.parameter]; if (log.isDebugEnabled()) { - log.debug("\tPermission test on node " + nodeService.getPath(testNodeRef)); + if (nodeService.exists(testNodeRef)) + { + log.debug("\tPermission test on node " + nodeService.getPath(testNodeRef)); + } + else + { + log.debug("\tPermission test on non-existing node " +testNodeRef); + } + } } else if (ChildAssociationRef.class.isAssignableFrom(params[cad.parameter])) @@ -263,7 +267,14 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean testNodeRef = ((ChildAssociationRef) invocation.getArguments()[cad.parameter]).getChildRef(); if (log.isDebugEnabled()) { - log.debug("\tPermission test on node " + nodeService.getPath(testNodeRef)); + if (nodeService.exists(testNodeRef)) + { + log.debug("\tPermission test on node " + nodeService.getPath(testNodeRef)); + } + else + { + log.debug("\tPermission test on non-existing node " + testNodeRef); + } } } } @@ -284,6 +295,14 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean testNodeRef = nodeService.getPrimaryParent(child).getParentRef(); if (log.isDebugEnabled()) { + if (nodeService.exists(testNodeRef)) + { + log.debug("\tPermission test for parent on node " + nodeService.getPath(testNodeRef)); + } + else + { + log.debug("\tPermission test for parent on non-existing node " + testNodeRef); + } log.debug("\tPermission test for parent on node " + nodeService.getPath(testNodeRef)); } } @@ -295,8 +314,17 @@ public class ACLEntryVoter implements AccessDecisionVoter, InitializingBean testNodeRef = ((ChildAssociationRef) invocation.getArguments()[cad.parameter]).getParentRef(); if (log.isDebugEnabled()) { - log.debug("\tPermission test for parent on child assoc ref for node " - + nodeService.getPath(testNodeRef)); + if (nodeService.exists(testNodeRef)) + { + log.debug("\tPermission test for parent on child assoc ref for node " + + nodeService.getPath(testNodeRef)); + } + else + { + log.debug("\tPermission test for parent on child assoc ref for non existing node " + + testNodeRef); + } + } } diff --git a/source/java/org/alfresco/repo/security/permissions/impl/model/PermissionModel.java b/source/java/org/alfresco/repo/security/permissions/impl/model/PermissionModel.java index e9a87a88f0..d7c6938916 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/model/PermissionModel.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/model/PermissionModel.java @@ -1,966 +1,966 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.repo.security.permissions.impl.model; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -import org.alfresco.repo.security.permissions.PermissionEntry; -import org.alfresco.repo.security.permissions.PermissionReference; -import org.alfresco.repo.security.permissions.impl.ModelDAO; -import org.alfresco.repo.security.permissions.impl.RequiredPermission; -import org.alfresco.repo.security.permissions.impl.SimplePermissionReference; -import org.alfresco.service.cmr.dictionary.AspectDefinition; -import org.alfresco.service.cmr.dictionary.ClassDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.dictionary.TypeDefinition; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.namespace.DynamicNamespacePrefixResolver; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.dom4j.Attribute; -import org.dom4j.Document; -import org.dom4j.DocumentException; -import org.dom4j.Element; -import org.dom4j.io.SAXReader; -import org.springframework.beans.factory.InitializingBean; - -/** - * The implementation of the model DAO Reads and stores the top level model information Encapsulates access to this information - * - * @author andyh - */ -public class PermissionModel implements ModelDAO, InitializingBean -{ - // IOC - - private NodeService nodeService; - - private DictionaryService dictionaryService; - - // XML Constants - - private static final String NAMESPACES = "namespaces"; - - private static final String NAMESPACE = "namespace"; - - private static final String NAMESPACE_URI = "uri"; - - private static final String NAMESPACE_PREFIX = "prefix"; - - private static final String PERMISSION_SET = "permissionSet"; - - private static final String GLOBAL_PERMISSION = "globalPermission"; - - private static final String DENY = "deny"; - - private static final String ALLOW = "allow"; - - private static final String DEFAULT_PERMISSION = "defaultPermission"; - - // Instance variables - - private String model; - - private Map permissionSets = new HashMap(); - - private Set globalPermissions = new HashSet(); - - private AccessStatus defaultPermission; - - // Cache granting permissions - private HashMap> grantingPermissions = new HashMap>(); - - // Cache grantees - private HashMap> granteePermissions = new HashMap>(); - - // Cache the mapping of extended groups to the base - private HashMap groupsToBaseGroup = new HashMap(); - - private HashMap uniqueMap; - - private HashMap permissionMap; - - private HashMap permissionGroupMap; - - private HashMap permissionReferenceMap; - - private Map> cachedTypePermissionsExposed = new HashMap>( - 128, 1.0f); - - private Map> cachedTypePermissionsUnexposed = new HashMap>( - 128, 1.0f); - - public PermissionModel() - { - super(); - } - - // IOC - - public void setModel(String model) - { - this.model = model; - } - - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /* - * Initialise from file (non-Javadoc) - * - * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() - */ - - public void afterPropertiesSet() - { - Document document = createDocument(model); - Element root = document.getRootElement(); - - Attribute defaultPermissionAttribute = root.attribute(DEFAULT_PERMISSION); - if (defaultPermissionAttribute != null) - { - if (defaultPermissionAttribute.getStringValue().equalsIgnoreCase(ALLOW)) - { - defaultPermission = AccessStatus.ALLOWED; - } - else if (defaultPermissionAttribute.getStringValue().equalsIgnoreCase(DENY)) - { - defaultPermission = AccessStatus.DENIED; - } - else - { - throw new PermissionModelException("The default permission must be deny or allow"); - } - } - else - { - defaultPermission = AccessStatus.DENIED; - } - - DynamicNamespacePrefixResolver nspr = new DynamicNamespacePrefixResolver(); - - // Namespaces - - for (Iterator nsit = root.elementIterator(NAMESPACES); nsit.hasNext(); /**/) - { - Element namespacesElement = (Element) nsit.next(); - for (Iterator it = namespacesElement.elementIterator(NAMESPACE); it.hasNext(); /**/) - { - Element nameSpaceElement = (Element) it.next(); - nspr.registerNamespace(nameSpaceElement.attributeValue(NAMESPACE_PREFIX), nameSpaceElement - .attributeValue(NAMESPACE_URI)); - } - } - - // Permission Sets - - for (Iterator psit = root.elementIterator(PERMISSION_SET); psit.hasNext(); /**/) - { - Element permissionSetElement = (Element) psit.next(); - PermissionSet permissionSet = new PermissionSet(); - permissionSet.initialise(permissionSetElement, nspr, this); - - permissionSets.put(permissionSet.getQName(), permissionSet); - } - - buildUniquePermissionMap(); - - // NodePermissions - - for (Iterator npit = root.elementIterator(GLOBAL_PERMISSION); npit.hasNext(); /**/) - { - Element globalPermissionElement = (Element) npit.next(); - GlobalPermissionEntry globalPermission = new GlobalPermissionEntry(); - globalPermission.initialise(globalPermissionElement, nspr, this); - - globalPermissions.add(globalPermission); - } - } - - /* - * Create the XML document from the file location - */ - private Document createDocument(String model) - { - InputStream is = this.getClass().getClassLoader().getResourceAsStream(model); - if (is == null) - { - throw new PermissionModelException("File not found: " + model); - } - SAXReader reader = new SAXReader(); - try - { - Document document = reader.read(is); - is.close(); - return document; - } - catch (DocumentException e) - { - throw new PermissionModelException("Failed to create permission model document ", e); - } - catch (IOException e) - { - throw new PermissionModelException("Failed to close permission model document ", e); - } - - } - - public AccessStatus getDefaultPermission() - { - return defaultPermission; - } - - public AccessStatus getDefaultPermission(PermissionReference pr) - { - Permission p = permissionMap.get(pr); - if (p == null) - { - return defaultPermission; - } - else - { - return p.getDefaultPermission(); - } - } - - public Set getGlobalPermissionEntries() - { - return Collections.unmodifiableSet(globalPermissions); - } - - public Map getPermissionSets() - { - return Collections.unmodifiableMap(permissionSets); - } - - public Set getAllPermissions(QName type) - { - return getAllPermissionsImpl(type, false); - } - - public Set getExposedPermissions(QName type) - { - return getAllPermissionsImpl(type, true); - } - - @SuppressWarnings("unchecked") - private Set getAllPermissionsImpl(QName type, boolean exposedOnly) - { - Map> cache; - if (exposedOnly) - { - cache = this.cachedTypePermissionsExposed; - } - else - { - cache = this.cachedTypePermissionsUnexposed; - } - LinkedHashSet permissions = cache.get(type); - if (permissions == null) - { - permissions = new LinkedHashSet(); - ClassDefinition cd = dictionaryService.getClass(type); - if (cd != null) - { - if (cd.isAspect()) - { - addAspectPermissions(type, permissions, exposedOnly); - } - else - { - mergeGeneralAspectPermissions(permissions, exposedOnly); - addTypePermissions(type, permissions, exposedOnly); - } - } - cache.put(type, permissions); - } - return (Set) permissions.clone(); - } - - /** - * Support to add permissions for types - * - * @param type - * @param permissions - */ - private void addTypePermissions(QName type, Set permissions, boolean exposedOnly) - { - TypeDefinition typeDef = dictionaryService.getType(type); - if (typeDef.getParentName() != null) - { - PermissionSet permissionSet = permissionSets.get(type); - if (!exposedOnly || (permissionSet == null) || permissionSet.exposeAll()) - { - addTypePermissions(typeDef.getParentName(), permissions, exposedOnly); - } - } - for (AspectDefinition ad : typeDef.getDefaultAspects()) - { - addAspectPermissions(ad.getName(), permissions, exposedOnly); - } - mergePermissions(permissions, type, exposedOnly, true); - } - - /** - * Support to add permissions for aspects. - * - * @param type - * @param permissions - */ - private void addAspectPermissions(QName type, Set permissions, boolean exposedOnly) - { - AspectDefinition aspectDef = dictionaryService.getAspect(type); - if (aspectDef.getParentName() != null) - { - PermissionSet permissionSet = permissionSets.get(type); - if (!exposedOnly || (permissionSet == null) || permissionSet.exposeAll()) - { - addAspectPermissions(aspectDef.getParentName(), permissions, exposedOnly); - } - } - mergePermissions(permissions, type, exposedOnly, true); - } - - /** - * Support to merge permissions together. Respects extended permissions. - * - * @param target - * @param type - */ - private void mergePermissions(Set target, QName type, boolean exposedOnly, boolean typeRequired) - { - PermissionSet permissionSet = permissionSets.get(type); - if (permissionSet != null) - { - for (PermissionGroup pg : permissionSet.getPermissionGroups()) - { - if (!exposedOnly || permissionSet.exposeAll() || pg.isExposed()) - { - if (!pg.isExtends()) - { - if (pg.isTypeRequired() == typeRequired) - { - target.add(pg); - } - } - else if (exposedOnly) - { - if (pg.isTypeRequired() == typeRequired) - { - target.add(getBasePermissionGroup(pg)); - } - } - } - } - for (Permission p : permissionSet.getPermissions()) - { - if (!exposedOnly || permissionSet.exposeAll() || p.isExposed()) - { - if (p.isTypeRequired() == typeRequired) - { - target.add(p); - } - } - } - } - } - - private void mergeGeneralAspectPermissions(Set target, boolean exposedOnly) - { - for (QName aspect : dictionaryService.getAllAspects()) - { - mergePermissions(target, aspect, exposedOnly, false); - } - } - - public Set getAllPermissions(NodeRef nodeRef) - { - return getExposedPermissionsImpl(nodeRef, false); - } - - public Set getExposedPermissions(NodeRef nodeRef) - { - return getExposedPermissionsImpl(nodeRef, true); - } - - public Set getExposedPermissionsImpl(NodeRef nodeRef, boolean exposedOnly) - { - // - // TODO: cache permissions based on type and exposed flag - // create JMeter test to see before/after effect! - // - QName typeName = nodeService.getType(nodeRef); - - Set permissions = getAllPermissions(typeName); - mergeGeneralAspectPermissions(permissions, exposedOnly); - // Add non mandatory aspects... - Set defaultAspects = new HashSet(); - for (AspectDefinition aspDef : dictionaryService.getType(typeName).getDefaultAspects()) - { - defaultAspects.add(aspDef.getName()); - } - for (QName aspect : nodeService.getAspects(nodeRef)) - { - if (!defaultAspects.contains(aspect)) - { - addAspectPermissions(aspect, permissions, exposedOnly); - } - } - return permissions; - } - - public synchronized Set getGrantingPermissions(PermissionReference permissionReference) - { - // Cache the results - Set granters = grantingPermissions.get(permissionReference); - if (granters == null) - { - granters = getGrantingPermissionsImpl(permissionReference); - grantingPermissions.put(permissionReference, granters); - } - return granters; - } - - private Set getGrantingPermissionsImpl(PermissionReference permissionReference) - { - // Query the model - HashSet permissions = new HashSet(); - permissions.add(permissionReference); - for (PermissionSet ps : permissionSets.values()) - { - for (PermissionGroup pg : ps.getPermissionGroups()) - { - if (grants(pg, permissionReference)) - { - permissions.add(getBasePermissionGroup(pg)); - } - if (pg.isAllowFullControl()) - { - permissions.add(pg); - } - } - for (Permission p : ps.getPermissions()) - { - if (p.equals(permissionReference)) - { - for (PermissionReference pg : p.getGrantedToGroups()) - { - permissions.add(getBasePermissionGroup(getPermissionGroup(pg))); - } - } - for (RequiredPermission rp : p.getRequiredPermissions()) - { - if (rp.equals(permissionReference) && rp.isImplies()) - { - permissions.add(p); - break; - } - } - } - } - return permissions; - } - - private boolean grants(PermissionGroup pg, PermissionReference permissionReference) - { - if (pg.getIncludedPermissionGroups().contains(permissionReference)) - { - return true; - } - if (getGranteePermissions(pg).contains(permissionReference)) - { - return true; - } - for (PermissionReference nested : pg.getIncludedPermissionGroups()) - { - if (grants(getPermissionGroup(nested), permissionReference)) - { - return true; - } - } - return false; - } - - public synchronized Set getGranteePermissions(PermissionReference permissionReference) - { - // Cache the results - Set grantees = granteePermissions.get(permissionReference); - if (grantees == null) - { - grantees = getGranteePermissionsImpl(permissionReference); - granteePermissions.put(permissionReference, grantees); - } - return grantees; - } - - private Set getGranteePermissionsImpl(PermissionReference permissionReference) - { - // Query the model - HashSet permissions = new HashSet(); - permissions.add(permissionReference); - for (PermissionSet ps : permissionSets.values()) - { - for (PermissionGroup pg : ps.getPermissionGroups()) - { - if (pg.equals(permissionReference)) - { - for (PermissionReference included : pg.getIncludedPermissionGroups()) - { - permissions.addAll(getGranteePermissions(included)); - } - - if (pg.isExtends()) - { - if (pg.getTypeQName() != null) - { - permissions.addAll(getGranteePermissions(new SimplePermissionReference(pg.getTypeQName(), - pg.getName()))); - } - else - { - ClassDefinition classDefinition = dictionaryService.getClass(pg.getQName()); - QName parent = classDefinition.getParentName(); - if (parent != null) - { - classDefinition = dictionaryService.getClass(parent); - PermissionGroup attempt = getPermissionGroupOrNull(new SimplePermissionReference( - parent, pg.getName())); - if (attempt != null) - { - permissions.addAll(getGranteePermissions(attempt)); - } - } - } - } - - if (pg.isAllowFullControl()) - { - // add all available - permissions.addAll(getAllPermissions()); - } - } - } - PermissionGroup baseGroup = getBasePermissionGroupOrNull(getPermissionGroupOrNull(permissionReference)); - if (baseGroup != null) - { - for (Permission p : ps.getPermissions()) - { - for (PermissionReference grantedTo : p.getGrantedToGroups()) - { - PermissionGroup base = getBasePermissionGroupOrNull(getPermissionGroupOrNull(grantedTo)); - if (baseGroup.equals(base)) - { - permissions.add(p); - } - } - } - } - } - return permissions; - } - - private Set getAllPermissions() - { - HashSet permissions = new HashSet(); - for (PermissionSet ps : permissionSets.values()) - { - for (PermissionGroup pg : ps.getPermissionGroups()) - { - permissions.add(pg); - } - for (Permission p : ps.getPermissions()) - { - permissions.add(p); - } - } - return permissions; - } - - /** - * Support to find permission groups - * - * @param target - * @return - */ - private PermissionGroup getPermissionGroupOrNull(PermissionReference target) - { - PermissionGroup pg = permissionGroupMap.get(target); - return pg == null ? null : pg; - } - - /** - * Support to get a permission group - * - * @param target - * @return - */ - private PermissionGroup getPermissionGroup(PermissionReference target) - { - PermissionGroup pg = getPermissionGroupOrNull(target); - if (pg == null) - { - throw new PermissionModelException("There is no permission group :" - + target.getQName() + " " + target.getName()); - } - return pg; - } - - /** - * Get the base permission group for a given permission group. - * - * @param pg - * @return - */ - private synchronized PermissionGroup getBasePermissionGroupOrNull(PermissionGroup pg) - { - if (groupsToBaseGroup.containsKey(pg)) - { - return groupsToBaseGroup.get(pg); - } - else - { - PermissionGroup answer = getBasePermissionGroupOrNullImpl(pg); - groupsToBaseGroup.put(pg, answer); - return answer; - } - } - - /** - * Query the model for a base permission group Uses the Data Dictionary to reolve inheritance - * - * @param pg - * @return - */ - private PermissionGroup getBasePermissionGroupOrNullImpl(PermissionGroup pg) - { - if (pg == null) - { - return null; - } - if (pg.isExtends()) - { - if (pg.getTypeQName() != null) - { - return getPermissionGroup(new SimplePermissionReference(pg.getTypeQName(), pg.getName())); - } - else - { - ClassDefinition classDefinition = dictionaryService.getClass(pg.getQName()); - QName parent; - while ((parent = classDefinition.getParentName()) != null) - { - classDefinition = dictionaryService.getClass(parent); - PermissionGroup attempt = getPermissionGroupOrNull(new SimplePermissionReference(parent, pg - .getName())); - if ((attempt != null) && (!attempt.isExtends())) - { - return attempt; - } - } - return null; - } - } - else - { - return pg; - } - } - - private PermissionGroup getBasePermissionGroup(PermissionGroup target) - { - PermissionGroup pg = getBasePermissionGroupOrNull(target); - if (pg == null) - { - throw new PermissionModelException("There is no parent for permission group :" - + target.getQName() + " " + target.getName()); - } - return pg; - } - - public Set getRequiredPermissions(PermissionReference required, QName qName, - Set aspectQNames, RequiredPermission.On on) - { - PermissionGroup pg = getBasePermissionGroupOrNull(getPermissionGroupOrNull(required)); - if (pg == null) - { - return getRequirementsForPermission(required, on); - } - else - { - return getRequirementsForPermissionGroup(pg, on, qName, aspectQNames); - } - } - - /** - * Get the requirements for a permission - * - * @param required - * @param on - * @return - */ - private Set getRequirementsForPermission(PermissionReference required, RequiredPermission.On on) - { - HashSet requiredPermissions = new HashSet(); - Permission p = getPermissionOrNull(required); - if (p != null) - { - for (RequiredPermission rp : p.getRequiredPermissions()) - { - if (!rp.isImplies() && rp.getOn().equals(on)) - { - requiredPermissions.add(rp); - } - } - } - return requiredPermissions; - } - - /** - * Get the requirements for a permission set - * - * @param target - * @param on - * @param qName - * @param aspectQNames - * @return - */ - private Set getRequirementsForPermissionGroup(PermissionGroup target, - RequiredPermission.On on, QName qName, Set aspectQNames) - { - HashSet requiredPermissions = new HashSet(); - if (target == null) - { - return requiredPermissions; - } - for (PermissionSet ps : permissionSets.values()) - { - for (PermissionGroup pg : ps.getPermissionGroups()) - { - PermissionGroup base = getBasePermissionGroupOrNull(pg); - if (target.equals(base) - && (!base.isTypeRequired() || isPartOfDynamicPermissionGroup(pg, qName, aspectQNames))) - { - // Add includes - for (PermissionReference pr : pg.getIncludedPermissionGroups()) - { - requiredPermissions.addAll(getRequirementsForPermissionGroup( - getBasePermissionGroupOrNull(getPermissionGroupOrNull(pr)), on, qName, aspectQNames)); - } - } - } - for (Permission p : ps.getPermissions()) - { - for (PermissionReference grantedTo : p.getGrantedToGroups()) - { - PermissionGroup base = getBasePermissionGroupOrNull(getPermissionGroupOrNull(grantedTo)); - if (target.equals(base) - && (!base.isTypeRequired() || isPartOfDynamicPermissionGroup(grantedTo, qName, aspectQNames))) - { - if (on == RequiredPermission.On.NODE) - { - requiredPermissions.add(p); - } - } - } - } - } - return requiredPermissions; - } - - /** - * Check type specifc extension of permission sets. - * - * @param pr - * @param typeQname - * @param aspects - * @return - */ - private boolean isPartOfDynamicPermissionGroup(PermissionReference pr, QName typeQname, Set aspects) - { - if (dictionaryService.isSubClass(typeQname, pr.getQName())) - { - return true; - } - for (QName aspect : aspects) - { - if (dictionaryService.isSubClass(aspect, pr.getQName())) - { - return true; - } - } - return false; - } - - /** - * Utility method to find a permission - * - * @param perm - * @return - */ - private Permission getPermissionOrNull(PermissionReference perm) - { - Permission p = permissionMap.get(perm); - return p == null ? null : p; - } - - public boolean checkPermission(PermissionReference required) - { - Permission permission = getPermissionOrNull(required); - if (permission != null) - { - return true; - } - PermissionGroup pg = getPermissionGroupOrNull(required); - if (pg != null) - { - if (pg.isExtends()) - { - if (pg.getTypeQName() != null) - { - return checkPermission(new SimplePermissionReference(pg.getTypeQName(), pg.getName())); - } - else - { - ClassDefinition classDefinition = dictionaryService.getClass(pg.getQName()); - QName parent; - while ((parent = classDefinition.getParentName()) != null) - { - classDefinition = dictionaryService.getClass(parent); - PermissionGroup attempt = getPermissionGroupOrNull(new SimplePermissionReference(parent, pg - .getName())); - if ((attempt != null) && attempt.isAllowFullControl()) - { - return true; - } - } - return false; - } - } - else - { - return pg.isAllowFullControl(); - } - } - else - { - return false; - } - - } - - public PermissionReference getPermissionReference(QName qname, String permissionName) - { - if (permissionName == null) - { - return null; - } - PermissionReference pr = uniqueMap.get(permissionName); - if (pr == null) - { - pr = permissionReferenceMap.get(permissionName); - if (pr == null) - { - throw new UnsupportedOperationException("Can not find " + permissionName); - } - } - return pr; - - } - - public boolean isUnique(PermissionReference permissionReference) - { - return uniqueMap.containsKey(permissionReference.getName()); - } - - private void buildUniquePermissionMap() - { - Set excluded = new HashSet(); - uniqueMap = new HashMap(); - permissionReferenceMap = new HashMap(); - permissionGroupMap = new HashMap(); - permissionMap = new HashMap(); - for (PermissionSet ps : permissionSets.values()) - { - for (PermissionGroup pg : ps.getPermissionGroups()) - { - if (uniqueMap.containsKey(pg.getName()) && !excluded.contains(pg.getName())) - { - PermissionReference value = uniqueMap.get(pg.getName()); - if (!value.equals(getBasePermissionGroup(pg))) - { - uniqueMap.remove(pg.getName()); - excluded.add(pg.getName()); - } - } - else - { - uniqueMap.put(pg.getName(), getBasePermissionGroup(pg)); - } - permissionReferenceMap.put(pg.toString(), pg); - permissionGroupMap.put(pg, pg); - } - for (Permission p : ps.getPermissions()) - { - if (uniqueMap.containsKey(p.getName()) && !excluded.contains(p.getName())) - { - PermissionReference value = uniqueMap.get(p.getName()); - if (!value.equals(p)) - { - uniqueMap.remove(p.getName()); - excluded.add(p.getName()); - } - } - else - { - uniqueMap.put(p.getName(), p); - } - permissionReferenceMap.put(p.toString(), p); - permissionMap.put(p, p); - } - } - // Add all permissions to the unique list - if (uniqueMap.containsKey(PermissionService.ALL_PERMISSIONS)) - { - throw new IllegalStateException( - "There must not be a permission with the same name as the ALL_PERMISSION constant: " - + PermissionService.ALL_PERMISSIONS); - } - uniqueMap.put(PermissionService.ALL_PERMISSIONS, new SimplePermissionReference(QName.createQName( - NamespaceService.SECURITY_MODEL_1_0_URI, PermissionService.ALL_PERMISSIONS), - PermissionService.ALL_PERMISSIONS)); - - } - -} +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.security.permissions.impl.model; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.alfresco.repo.security.permissions.PermissionEntry; +import org.alfresco.repo.security.permissions.PermissionReference; +import org.alfresco.repo.security.permissions.impl.ModelDAO; +import org.alfresco.repo.security.permissions.impl.RequiredPermission; +import org.alfresco.repo.security.permissions.impl.SimplePermissionReference; +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.DynamicNamespacePrefixResolver; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.dom4j.Attribute; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.dom4j.io.SAXReader; +import org.springframework.beans.factory.InitializingBean; + +/** + * The implementation of the model DAO Reads and stores the top level model information Encapsulates access to this information + * + * @author andyh + */ +public class PermissionModel implements ModelDAO, InitializingBean +{ + // IOC + + private NodeService nodeService; + + private DictionaryService dictionaryService; + + // XML Constants + + private static final String NAMESPACES = "namespaces"; + + private static final String NAMESPACE = "namespace"; + + private static final String NAMESPACE_URI = "uri"; + + private static final String NAMESPACE_PREFIX = "prefix"; + + private static final String PERMISSION_SET = "permissionSet"; + + private static final String GLOBAL_PERMISSION = "globalPermission"; + + private static final String DENY = "deny"; + + private static final String ALLOW = "allow"; + + private static final String DEFAULT_PERMISSION = "defaultPermission"; + + // Instance variables + + private String model; + + private Map permissionSets = new HashMap(); + + private Set globalPermissions = new HashSet(); + + private AccessStatus defaultPermission; + + // Cache granting permissions + private HashMap> grantingPermissions = new HashMap>(); + + // Cache grantees + private HashMap> granteePermissions = new HashMap>(); + + // Cache the mapping of extended groups to the base + private HashMap groupsToBaseGroup = new HashMap(); + + private HashMap uniqueMap; + + private HashMap permissionMap; + + private HashMap permissionGroupMap; + + private HashMap permissionReferenceMap; + + private Map> cachedTypePermissionsExposed = new HashMap>( + 128, 1.0f); + + private Map> cachedTypePermissionsUnexposed = new HashMap>( + 128, 1.0f); + + public PermissionModel() + { + super(); + } + + // IOC + + public void setModel(String model) + { + this.model = model; + } + + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /* + * Initialise from file (non-Javadoc) + * + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + + public void afterPropertiesSet() + { + Document document = createDocument(model); + Element root = document.getRootElement(); + + Attribute defaultPermissionAttribute = root.attribute(DEFAULT_PERMISSION); + if (defaultPermissionAttribute != null) + { + if (defaultPermissionAttribute.getStringValue().equalsIgnoreCase(ALLOW)) + { + defaultPermission = AccessStatus.ALLOWED; + } + else if (defaultPermissionAttribute.getStringValue().equalsIgnoreCase(DENY)) + { + defaultPermission = AccessStatus.DENIED; + } + else + { + throw new PermissionModelException("The default permission must be deny or allow"); + } + } + else + { + defaultPermission = AccessStatus.DENIED; + } + + DynamicNamespacePrefixResolver nspr = new DynamicNamespacePrefixResolver(); + + // Namespaces + + for (Iterator nsit = root.elementIterator(NAMESPACES); nsit.hasNext(); /**/) + { + Element namespacesElement = (Element) nsit.next(); + for (Iterator it = namespacesElement.elementIterator(NAMESPACE); it.hasNext(); /**/) + { + Element nameSpaceElement = (Element) it.next(); + nspr.registerNamespace(nameSpaceElement.attributeValue(NAMESPACE_PREFIX), nameSpaceElement + .attributeValue(NAMESPACE_URI)); + } + } + + // Permission Sets + + for (Iterator psit = root.elementIterator(PERMISSION_SET); psit.hasNext(); /**/) + { + Element permissionSetElement = (Element) psit.next(); + PermissionSet permissionSet = new PermissionSet(); + permissionSet.initialise(permissionSetElement, nspr, this); + + permissionSets.put(permissionSet.getQName(), permissionSet); + } + + buildUniquePermissionMap(); + + // NodePermissions + + for (Iterator npit = root.elementIterator(GLOBAL_PERMISSION); npit.hasNext(); /**/) + { + Element globalPermissionElement = (Element) npit.next(); + GlobalPermissionEntry globalPermission = new GlobalPermissionEntry(); + globalPermission.initialise(globalPermissionElement, nspr, this); + + globalPermissions.add(globalPermission); + } + } + + /* + * Create the XML document from the file location + */ + private Document createDocument(String model) + { + InputStream is = this.getClass().getClassLoader().getResourceAsStream(model); + if (is == null) + { + throw new PermissionModelException("File not found: " + model); + } + SAXReader reader = new SAXReader(); + try + { + Document document = reader.read(is); + is.close(); + return document; + } + catch (DocumentException e) + { + throw new PermissionModelException("Failed to create permission model document ", e); + } + catch (IOException e) + { + throw new PermissionModelException("Failed to close permission model document ", e); + } + + } + + public AccessStatus getDefaultPermission() + { + return defaultPermission; + } + + public AccessStatus getDefaultPermission(PermissionReference pr) + { + Permission p = permissionMap.get(pr); + if (p == null) + { + return defaultPermission; + } + else + { + return p.getDefaultPermission(); + } + } + + public Set getGlobalPermissionEntries() + { + return Collections.unmodifiableSet(globalPermissions); + } + + public Map getPermissionSets() + { + return Collections.unmodifiableMap(permissionSets); + } + + public Set getAllPermissions(QName type) + { + return getAllPermissionsImpl(type, false); + } + + public Set getExposedPermissions(QName type) + { + return getAllPermissionsImpl(type, true); + } + + @SuppressWarnings("unchecked") + private Set getAllPermissionsImpl(QName type, boolean exposedOnly) + { + Map> cache; + if (exposedOnly) + { + cache = this.cachedTypePermissionsExposed; + } + else + { + cache = this.cachedTypePermissionsUnexposed; + } + LinkedHashSet permissions = cache.get(type); + if (permissions == null) + { + permissions = new LinkedHashSet(); + ClassDefinition cd = dictionaryService.getClass(type); + if (cd != null) + { + if (cd.isAspect()) + { + addAspectPermissions(type, permissions, exposedOnly); + } + else + { + mergeGeneralAspectPermissions(permissions, exposedOnly); + addTypePermissions(type, permissions, exposedOnly); + } + } + cache.put(type, permissions); + } + return (Set) permissions.clone(); + } + + /** + * Support to add permissions for types + * + * @param type + * @param permissions + */ + private void addTypePermissions(QName type, Set permissions, boolean exposedOnly) + { + TypeDefinition typeDef = dictionaryService.getType(type); + if (typeDef.getParentName() != null) + { + PermissionSet permissionSet = permissionSets.get(type); + if (!exposedOnly || (permissionSet == null) || permissionSet.exposeAll()) + { + addTypePermissions(typeDef.getParentName(), permissions, exposedOnly); + } + } + for (AspectDefinition ad : typeDef.getDefaultAspects()) + { + addAspectPermissions(ad.getName(), permissions, exposedOnly); + } + mergePermissions(permissions, type, exposedOnly, true); + } + + /** + * Support to add permissions for aspects. + * + * @param type + * @param permissions + */ + private void addAspectPermissions(QName type, Set permissions, boolean exposedOnly) + { + AspectDefinition aspectDef = dictionaryService.getAspect(type); + if (aspectDef.getParentName() != null) + { + PermissionSet permissionSet = permissionSets.get(type); + if (!exposedOnly || (permissionSet == null) || permissionSet.exposeAll()) + { + addAspectPermissions(aspectDef.getParentName(), permissions, exposedOnly); + } + } + mergePermissions(permissions, type, exposedOnly, true); + } + + /** + * Support to merge permissions together. Respects extended permissions. + * + * @param target + * @param type + */ + private void mergePermissions(Set target, QName type, boolean exposedOnly, boolean typeRequired) + { + PermissionSet permissionSet = permissionSets.get(type); + if (permissionSet != null) + { + for (PermissionGroup pg : permissionSet.getPermissionGroups()) + { + if (!exposedOnly || permissionSet.exposeAll() || pg.isExposed()) + { + if (!pg.isExtends()) + { + if (pg.isTypeRequired() == typeRequired) + { + target.add(pg); + } + } + else if (exposedOnly) + { + if (pg.isTypeRequired() == typeRequired) + { + target.add(getBasePermissionGroup(pg)); + } + } + } + } + for (Permission p : permissionSet.getPermissions()) + { + if (!exposedOnly || permissionSet.exposeAll() || p.isExposed()) + { + if (p.isTypeRequired() == typeRequired) + { + target.add(p); + } + } + } + } + } + + private void mergeGeneralAspectPermissions(Set target, boolean exposedOnly) + { + for (QName aspect : dictionaryService.getAllAspects()) + { + mergePermissions(target, aspect, exposedOnly, false); + } + } + + public Set getAllPermissions(NodeRef nodeRef) + { + return getExposedPermissionsImpl(nodeRef, false); + } + + public Set getExposedPermissions(NodeRef nodeRef) + { + return getExposedPermissionsImpl(nodeRef, true); + } + + public Set getExposedPermissionsImpl(NodeRef nodeRef, boolean exposedOnly) + { + // + // TODO: cache permissions based on type and exposed flag + // create JMeter test to see before/after effect! + // + QName typeName = nodeService.getType(nodeRef); + + Set permissions = getAllPermissions(typeName); + mergeGeneralAspectPermissions(permissions, exposedOnly); + // Add non mandatory aspects... + Set defaultAspects = new HashSet(); + for (AspectDefinition aspDef : dictionaryService.getType(typeName).getDefaultAspects()) + { + defaultAspects.add(aspDef.getName()); + } + for (QName aspect : nodeService.getAspects(nodeRef)) + { + if (!defaultAspects.contains(aspect)) + { + addAspectPermissions(aspect, permissions, exposedOnly); + } + } + return permissions; + } + + public synchronized Set getGrantingPermissions(PermissionReference permissionReference) + { + // Cache the results + Set granters = grantingPermissions.get(permissionReference); + if (granters == null) + { + granters = getGrantingPermissionsImpl(permissionReference); + grantingPermissions.put(permissionReference, granters); + } + return granters; + } + + private Set getGrantingPermissionsImpl(PermissionReference permissionReference) + { + // Query the model + HashSet permissions = new HashSet(); + permissions.add(permissionReference); + for (PermissionSet ps : permissionSets.values()) + { + for (PermissionGroup pg : ps.getPermissionGroups()) + { + if (grants(pg, permissionReference)) + { + permissions.add(getBasePermissionGroup(pg)); + } + if (pg.isAllowFullControl()) + { + permissions.add(pg); + } + } + for (Permission p : ps.getPermissions()) + { + if (p.equals(permissionReference)) + { + for (PermissionReference pg : p.getGrantedToGroups()) + { + permissions.add(getBasePermissionGroup(getPermissionGroup(pg))); + } + } + for (RequiredPermission rp : p.getRequiredPermissions()) + { + if (rp.equals(permissionReference) && rp.isImplies()) + { + permissions.add(p); + break; + } + } + } + } + return permissions; + } + + private boolean grants(PermissionGroup pg, PermissionReference permissionReference) + { + if (pg.getIncludedPermissionGroups().contains(permissionReference)) + { + return true; + } + if (getGranteePermissions(pg).contains(permissionReference)) + { + return true; + } + for (PermissionReference nested : pg.getIncludedPermissionGroups()) + { + if (grants(getPermissionGroup(nested), permissionReference)) + { + return true; + } + } + return false; + } + + public synchronized Set getGranteePermissions(PermissionReference permissionReference) + { + // Cache the results + Set grantees = granteePermissions.get(permissionReference); + if (grantees == null) + { + grantees = getGranteePermissionsImpl(permissionReference); + granteePermissions.put(permissionReference, grantees); + } + return grantees; + } + + private Set getGranteePermissionsImpl(PermissionReference permissionReference) + { + // Query the model + HashSet permissions = new HashSet(); + permissions.add(permissionReference); + for (PermissionSet ps : permissionSets.values()) + { + for (PermissionGroup pg : ps.getPermissionGroups()) + { + if (pg.equals(permissionReference)) + { + for (PermissionReference included : pg.getIncludedPermissionGroups()) + { + permissions.addAll(getGranteePermissions(included)); + } + + if (pg.isExtends()) + { + if (pg.getTypeQName() != null) + { + permissions.addAll(getGranteePermissions(new SimplePermissionReference(pg.getTypeQName(), + pg.getName()))); + } + else + { + ClassDefinition classDefinition = dictionaryService.getClass(pg.getQName()); + QName parent = classDefinition.getParentName(); + if (parent != null) + { + classDefinition = dictionaryService.getClass(parent); + PermissionGroup attempt = getPermissionGroupOrNull(new SimplePermissionReference( + parent, pg.getName())); + if (attempt != null) + { + permissions.addAll(getGranteePermissions(attempt)); + } + } + } + } + + if (pg.isAllowFullControl()) + { + // add all available + permissions.addAll(getAllPermissions()); + } + } + } + PermissionGroup baseGroup = getBasePermissionGroupOrNull(getPermissionGroupOrNull(permissionReference)); + if (baseGroup != null) + { + for (Permission p : ps.getPermissions()) + { + for (PermissionReference grantedTo : p.getGrantedToGroups()) + { + PermissionGroup base = getBasePermissionGroupOrNull(getPermissionGroupOrNull(grantedTo)); + if (baseGroup.equals(base)) + { + permissions.add(p); + } + } + } + } + } + return permissions; + } + + private Set getAllPermissions() + { + HashSet permissions = new HashSet(); + for (PermissionSet ps : permissionSets.values()) + { + for (PermissionGroup pg : ps.getPermissionGroups()) + { + permissions.add(pg); + } + for (Permission p : ps.getPermissions()) + { + permissions.add(p); + } + } + return permissions; + } + + /** + * Support to find permission groups + * + * @param target + * @return + */ + private PermissionGroup getPermissionGroupOrNull(PermissionReference target) + { + PermissionGroup pg = permissionGroupMap.get(target); + return pg == null ? null : pg; + } + + /** + * Support to get a permission group + * + * @param target + * @return + */ + private PermissionGroup getPermissionGroup(PermissionReference target) + { + PermissionGroup pg = getPermissionGroupOrNull(target); + if (pg == null) + { + throw new PermissionModelException("There is no permission group :" + + target.getQName() + " " + target.getName()); + } + return pg; + } + + /** + * Get the base permission group for a given permission group. + * + * @param pg + * @return + */ + private synchronized PermissionGroup getBasePermissionGroupOrNull(PermissionGroup pg) + { + if (groupsToBaseGroup.containsKey(pg)) + { + return groupsToBaseGroup.get(pg); + } + else + { + PermissionGroup answer = getBasePermissionGroupOrNullImpl(pg); + groupsToBaseGroup.put(pg, answer); + return answer; + } + } + + /** + * Query the model for a base permission group Uses the Data Dictionary to reolve inheritance + * + * @param pg + * @return + */ + private PermissionGroup getBasePermissionGroupOrNullImpl(PermissionGroup pg) + { + if (pg == null) + { + return null; + } + if (pg.isExtends()) + { + if (pg.getTypeQName() != null) + { + return getPermissionGroup(new SimplePermissionReference(pg.getTypeQName(), pg.getName())); + } + else + { + ClassDefinition classDefinition = dictionaryService.getClass(pg.getQName()); + QName parent; + while ((parent = classDefinition.getParentName()) != null) + { + classDefinition = dictionaryService.getClass(parent); + PermissionGroup attempt = getPermissionGroupOrNull(new SimplePermissionReference(parent, pg + .getName())); + if ((attempt != null) && (!attempt.isExtends())) + { + return attempt; + } + } + return null; + } + } + else + { + return pg; + } + } + + private PermissionGroup getBasePermissionGroup(PermissionGroup target) + { + PermissionGroup pg = getBasePermissionGroupOrNull(target); + if (pg == null) + { + throw new PermissionModelException("There is no parent for permission group :" + + target.getQName() + " " + target.getName()); + } + return pg; + } + + public Set getRequiredPermissions(PermissionReference required, QName qName, + Set aspectQNames, RequiredPermission.On on) + { + PermissionGroup pg = getBasePermissionGroupOrNull(getPermissionGroupOrNull(required)); + if (pg == null) + { + return getRequirementsForPermission(required, on); + } + else + { + return getRequirementsForPermissionGroup(pg, on, qName, aspectQNames); + } + } + + /** + * Get the requirements for a permission + * + * @param required + * @param on + * @return + */ + private Set getRequirementsForPermission(PermissionReference required, RequiredPermission.On on) + { + HashSet requiredPermissions = new HashSet(); + Permission p = getPermissionOrNull(required); + if (p != null) + { + for (RequiredPermission rp : p.getRequiredPermissions()) + { + if (!rp.isImplies() && rp.getOn().equals(on)) + { + requiredPermissions.add(rp); + } + } + } + return requiredPermissions; + } + + /** + * Get the requirements for a permission set + * + * @param target + * @param on + * @param qName + * @param aspectQNames + * @return + */ + private Set getRequirementsForPermissionGroup(PermissionGroup target, + RequiredPermission.On on, QName qName, Set aspectQNames) + { + HashSet requiredPermissions = new HashSet(); + if (target == null) + { + return requiredPermissions; + } + for (PermissionSet ps : permissionSets.values()) + { + for (PermissionGroup pg : ps.getPermissionGroups()) + { + PermissionGroup base = getBasePermissionGroupOrNull(pg); + if (target.equals(base) + && (!base.isTypeRequired() || isPartOfDynamicPermissionGroup(pg, qName, aspectQNames))) + { + // Add includes + for (PermissionReference pr : pg.getIncludedPermissionGroups()) + { + requiredPermissions.addAll(getRequirementsForPermissionGroup( + getBasePermissionGroupOrNull(getPermissionGroupOrNull(pr)), on, qName, aspectQNames)); + } + } + } + for (Permission p : ps.getPermissions()) + { + for (PermissionReference grantedTo : p.getGrantedToGroups()) + { + PermissionGroup base = getBasePermissionGroupOrNull(getPermissionGroupOrNull(grantedTo)); + if (target.equals(base) + && (!base.isTypeRequired() || isPartOfDynamicPermissionGroup(grantedTo, qName, aspectQNames))) + { + if (on == RequiredPermission.On.NODE) + { + requiredPermissions.add(p); + } + } + } + } + } + return requiredPermissions; + } + + /** + * Check type specifc extension of permission sets. + * + * @param pr + * @param typeQname + * @param aspects + * @return + */ + private boolean isPartOfDynamicPermissionGroup(PermissionReference pr, QName typeQname, Set aspects) + { + if (dictionaryService.isSubClass(typeQname, pr.getQName())) + { + return true; + } + for (QName aspect : aspects) + { + if (dictionaryService.isSubClass(aspect, pr.getQName())) + { + return true; + } + } + return false; + } + + /** + * Utility method to find a permission + * + * @param perm + * @return + */ + private Permission getPermissionOrNull(PermissionReference perm) + { + Permission p = permissionMap.get(perm); + return p == null ? null : p; + } + + public boolean checkPermission(PermissionReference required) + { + Permission permission = getPermissionOrNull(required); + if (permission != null) + { + return true; + } + PermissionGroup pg = getPermissionGroupOrNull(required); + if (pg != null) + { + if (pg.isExtends()) + { + if (pg.getTypeQName() != null) + { + return checkPermission(new SimplePermissionReference(pg.getTypeQName(), pg.getName())); + } + else + { + ClassDefinition classDefinition = dictionaryService.getClass(pg.getQName()); + QName parent; + while ((parent = classDefinition.getParentName()) != null) + { + classDefinition = dictionaryService.getClass(parent); + PermissionGroup attempt = getPermissionGroupOrNull(new SimplePermissionReference(parent, pg + .getName())); + if ((attempt != null) && attempt.isAllowFullControl()) + { + return true; + } + } + return false; + } + } + else + { + return pg.isAllowFullControl(); + } + } + else + { + return false; + } + + } + + public PermissionReference getPermissionReference(QName qname, String permissionName) + { + if (permissionName == null) + { + return null; + } + PermissionReference pr = uniqueMap.get(permissionName); + if (pr == null) + { + pr = permissionReferenceMap.get(permissionName); + if (pr == null) + { + throw new UnsupportedOperationException("Can not find " + permissionName); + } + } + return pr; + + } + + public boolean isUnique(PermissionReference permissionReference) + { + return uniqueMap.containsKey(permissionReference.getName()); + } + + private void buildUniquePermissionMap() + { + Set excluded = new HashSet(); + uniqueMap = new HashMap(); + permissionReferenceMap = new HashMap(); + permissionGroupMap = new HashMap(); + permissionMap = new HashMap(); + for (PermissionSet ps : permissionSets.values()) + { + for (PermissionGroup pg : ps.getPermissionGroups()) + { + if (uniqueMap.containsKey(pg.getName()) && !excluded.contains(pg.getName())) + { + PermissionReference value = uniqueMap.get(pg.getName()); + if (!value.equals(getBasePermissionGroup(pg))) + { + uniqueMap.remove(pg.getName()); + excluded.add(pg.getName()); + } + } + else + { + uniqueMap.put(pg.getName(), getBasePermissionGroup(pg)); + } + permissionReferenceMap.put(pg.toString(), pg); + permissionGroupMap.put(pg, pg); + } + for (Permission p : ps.getPermissions()) + { + if (uniqueMap.containsKey(p.getName()) && !excluded.contains(p.getName())) + { + PermissionReference value = uniqueMap.get(p.getName()); + if (!value.equals(p)) + { + uniqueMap.remove(p.getName()); + excluded.add(p.getName()); + } + } + else + { + uniqueMap.put(p.getName(), p); + } + permissionReferenceMap.put(p.toString(), p); + permissionMap.put(p, p); + } + } + // Add all permissions to the unique list + if (uniqueMap.containsKey(PermissionService.ALL_PERMISSIONS)) + { + throw new IllegalStateException( + "There must not be a permission with the same name as the ALL_PERMISSION constant: " + + PermissionService.ALL_PERMISSIONS); + } + uniqueMap.put(PermissionService.ALL_PERMISSIONS, new SimplePermissionReference(QName.createQName( + NamespaceService.SECURITY_MODEL_1_0_URI, PermissionService.ALL_PERMISSIONS), + PermissionService.ALL_PERMISSIONS)); + + } + +} diff --git a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java index ea5571f075..74d656bf1a 100644 --- a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java +++ b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java @@ -1,388 +1,388 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.repo.security.person; - -import java.io.Serializable; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.security.permissions.PermissionServiceSPI; -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.repository.datatype.DefaultTypeConverter; -import org.alfresco.service.cmr.search.ResultSet; -import org.alfresco.service.cmr.search.ResultSetRow; -import org.alfresco.service.cmr.search.SearchParameters; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.cmr.security.AuthorityService; -import org.alfresco.service.cmr.security.NoSuchPersonException; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.namespace.NamespacePrefixResolver; -import org.alfresco.service.namespace.QName; - -public class PersonServiceImpl implements PersonService -{ - public static final String SYSTEM_FOLDER = "/sys:system"; - - public static final String PEOPLE_FOLDER = SYSTEM_FOLDER + "/sys:people"; - - // IOC - - private StoreRef storeRef; - - private NodeService nodeService; - - private SearchService searchService; - - private AuthorityService authorityService; - - private PermissionServiceSPI permissionServiceSPI; - - private NamespacePrefixResolver namespacePrefixResolver; - - private boolean createMissingPeople; - - private static Set mutableProperties; - - private boolean userNamesAreCaseSensitive = false; - - private String defaultHomeFolderProvider; - - static - { - Set props = new HashSet(); - props.add(ContentModel.PROP_HOMEFOLDER); - props.add(ContentModel.PROP_FIRSTNAME); - // Middle Name - props.add(ContentModel.PROP_LASTNAME); - props.add(ContentModel.PROP_EMAIL); - props.add(ContentModel.PROP_ORGID); - mutableProperties = Collections.unmodifiableSet(props); - } - - public PersonServiceImpl() - { - super(); - } - - public boolean getUserNamesAreCaseSensitive() - { - return userNamesAreCaseSensitive; - } - - public void setUserNamesAreCaseSensitive(boolean userNamesAreCaseSensitive) - { - this.userNamesAreCaseSensitive = userNamesAreCaseSensitive; - } - - void setDefaultHomeFolderProvider(String defaultHomeFolderProvider) - { - this.defaultHomeFolderProvider = defaultHomeFolderProvider; - } - - public NodeRef getPerson(String userName) - { - NodeRef personNode = getPersonOrNull(userName); - if (personNode == null) - { - if (createMissingPeople()) - { - return createMissingPerson(userName); - } - else - { - throw new NoSuchPersonException(userName); - } - - } - else - { - return personNode; - } - } - - public boolean personExists(String caseSensitiveUserName) - { - return getPersonOrNull(caseSensitiveUserName) != null; - } - - public NodeRef getPersonOrNull(String searchUserName) - { - SearchParameters sp = new SearchParameters(); - sp.setLanguage(SearchService.LANGUAGE_LUCENE); - sp.setQuery("TYPE:\\{http\\://www.alfresco.org/model/content/1.0\\}person +@cm\\:userName:\"" + searchUserName - + "\""); - sp.addStore(storeRef); - sp.excludeDataInTheCurrentTransaction(false); - - ResultSet rs = null; - - try - { - rs = searchService.query(sp); - - NodeRef returnRef = null; - - for (ResultSetRow row : rs) - { - - NodeRef nodeRef = row.getNodeRef(); - if (nodeService.exists(nodeRef)) - { - String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty( - nodeRef, ContentModel.PROP_USERNAME)); - - if (userNamesAreCaseSensitive) - { - if (realUserName.equals(searchUserName)) - { - if (returnRef == null) - { - returnRef = nodeRef; - } - else - { - throw new AlfrescoRuntimeException("Found more than one user for " + searchUserName - + " (case sensitive)"); - } - } - } - else - { - if (realUserName.equalsIgnoreCase(searchUserName)) - { - if (returnRef == null) - { - returnRef = nodeRef; - } - else - { - throw new AlfrescoRuntimeException("Found more than one user for " + searchUserName - + " (case insensitive)"); - } - } - } - } - } - - return returnRef; - } - finally - { - if (rs != null) - { - rs.close(); - } - } - } - - public boolean createMissingPeople() - { - return createMissingPeople; - } - - public Set getMutableProperties() - { - return mutableProperties; - } - - public void setPersonProperties(String userName, Map properties) - { - NodeRef personNode = getPersonOrNull(userName); - if (personNode == null) - { - if (createMissingPeople()) - { - personNode = createMissingPerson(userName); - } - else - { - throw new PersonException("No person found for user name " + userName); - } - - } - else - { - String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(personNode, - ContentModel.PROP_USERNAME)); - properties.put(ContentModel.PROP_USERNAME, realUserName); - } - - nodeService.setProperties(personNode, properties); - } - - public boolean isMutable() - { - return true; - } - - private NodeRef createMissingPerson(String userName) - { - HashMap properties = getDefaultProperties(userName); - return createPerson(properties); - } - - private HashMap getDefaultProperties(String userName) - { - HashMap properties = new HashMap(); - properties.put(ContentModel.PROP_USERNAME, userName); - properties.put(ContentModel.PROP_FIRSTNAME, userName); - properties.put(ContentModel.PROP_LASTNAME, ""); - properties.put(ContentModel.PROP_EMAIL, ""); - properties.put(ContentModel.PROP_ORGID, ""); - properties.put(ContentModel.PROP_HOME_FOLDER_PROVIDER, defaultHomeFolderProvider); - return properties; - } - - public NodeRef createPerson(Map properties) - { - String userName = DefaultTypeConverter.INSTANCE.convert(String.class, properties - .get(ContentModel.PROP_USERNAME)); - properties.put(ContentModel.PROP_USERNAME, userName); - return nodeService.createNode(getPeopleContainer(), ContentModel.ASSOC_CHILDREN, ContentModel.TYPE_PERSON, - ContentModel.TYPE_PERSON, properties).getChildRef(); - } - - public NodeRef getPeopleContainer() - { - NodeRef rootNodeRef = nodeService.getRootNode(storeRef); - List results = searchService.selectNodes(rootNodeRef, PEOPLE_FOLDER, null, namespacePrefixResolver, - false); - if (results.size() == 0) - { - throw new AlfrescoRuntimeException("Required people system path not found: " + PEOPLE_FOLDER); - } - else - { - return results.get(0); - } - } - - public void deletePerson(String userName) - { - NodeRef personNodeRef = getPersonOrNull(userName); - - // delete the person - if (personNodeRef != null) - { - nodeService.deleteNode(personNodeRef); - } - - // remove user from any containing authorities - Set containerAuthorities = authorityService.getContainingAuthorities(null, userName, true); - for (String containerAuthority : containerAuthorities) - { - authorityService.removeAuthority(containerAuthority, userName); - } - - // remove any user permissions - permissionServiceSPI.deletePermissions(userName); - } - - public Set getAllPeople() - { - SearchParameters sp = new SearchParameters(); - sp.setLanguage(SearchService.LANGUAGE_LUCENE); - sp.setQuery("TYPE:\"" + ContentModel.TYPE_PERSON + "\""); - sp.addStore(storeRef); - sp.excludeDataInTheCurrentTransaction(false); - - LinkedHashSet nodes = new LinkedHashSet(); - ResultSet rs = null; - - try - { - rs = searchService.query(sp); - - for (ResultSetRow row : rs) - { - - NodeRef nodeRef = row.getNodeRef(); - if (nodeService.exists(nodeRef)) - { - nodes.add(nodeRef); - } - } - } - finally - { - if (rs != null) - { - rs.close(); - } - } - return nodes; - } - - public void setCreateMissingPeople(boolean createMissingPeople) - { - this.createMissingPeople = createMissingPeople; - } - - public void setNamespacePrefixResolver(NamespacePrefixResolver namespacePrefixResolver) - { - this.namespacePrefixResolver = namespacePrefixResolver; - } - - public void setAuthorityService(AuthorityService authorityService) - { - this.authorityService = authorityService; - } - - public void setPermissionServiceSPI(PermissionServiceSPI permissionServiceSPI) - { - this.permissionServiceSPI = permissionServiceSPI; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setSearchService(SearchService searchService) - { - this.searchService = searchService; - } - - public void setStoreUrl(String storeUrl) - { - this.storeRef = new StoreRef(storeUrl); - } - - public String getUserIdentifier(String caseSensitiveUserName) - { - NodeRef nodeRef = getPersonOrNull(caseSensitiveUserName); - if ((nodeRef != null) && nodeService.exists(nodeRef)) - { - String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, - ContentModel.PROP_USERNAME)); - return realUserName; - } - return null; - } - - // IOC Setters - -} +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.security.person; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.permissions.PermissionServiceSPI; +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.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.ResultSetRow; +import org.alfresco.service.cmr.search.SearchParameters; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.NoSuchPersonException; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.NamespacePrefixResolver; +import org.alfresco.service.namespace.QName; + +public class PersonServiceImpl implements PersonService +{ + public static final String SYSTEM_FOLDER = "/sys:system"; + + public static final String PEOPLE_FOLDER = SYSTEM_FOLDER + "/sys:people"; + + // IOC + + private StoreRef storeRef; + + private NodeService nodeService; + + private SearchService searchService; + + private AuthorityService authorityService; + + private PermissionServiceSPI permissionServiceSPI; + + private NamespacePrefixResolver namespacePrefixResolver; + + private boolean createMissingPeople; + + private static Set mutableProperties; + + private boolean userNamesAreCaseSensitive = false; + + private String defaultHomeFolderProvider; + + static + { + Set props = new HashSet(); + props.add(ContentModel.PROP_HOMEFOLDER); + props.add(ContentModel.PROP_FIRSTNAME); + // Middle Name + props.add(ContentModel.PROP_LASTNAME); + props.add(ContentModel.PROP_EMAIL); + props.add(ContentModel.PROP_ORGID); + mutableProperties = Collections.unmodifiableSet(props); + } + + public PersonServiceImpl() + { + super(); + } + + public boolean getUserNamesAreCaseSensitive() + { + return userNamesAreCaseSensitive; + } + + public void setUserNamesAreCaseSensitive(boolean userNamesAreCaseSensitive) + { + this.userNamesAreCaseSensitive = userNamesAreCaseSensitive; + } + + void setDefaultHomeFolderProvider(String defaultHomeFolderProvider) + { + this.defaultHomeFolderProvider = defaultHomeFolderProvider; + } + + public NodeRef getPerson(String userName) + { + NodeRef personNode = getPersonOrNull(userName); + if (personNode == null) + { + if (createMissingPeople()) + { + return createMissingPerson(userName); + } + else + { + throw new NoSuchPersonException(userName); + } + + } + else + { + return personNode; + } + } + + public boolean personExists(String caseSensitiveUserName) + { + return getPersonOrNull(caseSensitiveUserName) != null; + } + + public NodeRef getPersonOrNull(String searchUserName) + { + SearchParameters sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery("TYPE:\\{http\\://www.alfresco.org/model/content/1.0\\}person +@cm\\:userName:\"" + searchUserName + + "\""); + sp.addStore(storeRef); + sp.excludeDataInTheCurrentTransaction(false); + + ResultSet rs = null; + + try + { + rs = searchService.query(sp); + + NodeRef returnRef = null; + + for (ResultSetRow row : rs) + { + + NodeRef nodeRef = row.getNodeRef(); + if (nodeService.exists(nodeRef)) + { + String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty( + nodeRef, ContentModel.PROP_USERNAME)); + + if (userNamesAreCaseSensitive) + { + if (realUserName.equals(searchUserName)) + { + if (returnRef == null) + { + returnRef = nodeRef; + } + else + { + throw new AlfrescoRuntimeException("Found more than one user for " + searchUserName + + " (case sensitive)"); + } + } + } + else + { + if (realUserName.equalsIgnoreCase(searchUserName)) + { + if (returnRef == null) + { + returnRef = nodeRef; + } + else + { + throw new AlfrescoRuntimeException("Found more than one user for " + searchUserName + + " (case insensitive)"); + } + } + } + } + } + + return returnRef; + } + finally + { + if (rs != null) + { + rs.close(); + } + } + } + + public boolean createMissingPeople() + { + return createMissingPeople; + } + + public Set getMutableProperties() + { + return mutableProperties; + } + + public void setPersonProperties(String userName, Map properties) + { + NodeRef personNode = getPersonOrNull(userName); + if (personNode == null) + { + if (createMissingPeople()) + { + personNode = createMissingPerson(userName); + } + else + { + throw new PersonException("No person found for user name " + userName); + } + + } + else + { + String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(personNode, + ContentModel.PROP_USERNAME)); + properties.put(ContentModel.PROP_USERNAME, realUserName); + } + + nodeService.setProperties(personNode, properties); + } + + public boolean isMutable() + { + return true; + } + + private NodeRef createMissingPerson(String userName) + { + HashMap properties = getDefaultProperties(userName); + return createPerson(properties); + } + + private HashMap getDefaultProperties(String userName) + { + HashMap properties = new HashMap(); + properties.put(ContentModel.PROP_USERNAME, userName); + properties.put(ContentModel.PROP_FIRSTNAME, userName); + properties.put(ContentModel.PROP_LASTNAME, ""); + properties.put(ContentModel.PROP_EMAIL, ""); + properties.put(ContentModel.PROP_ORGID, ""); + properties.put(ContentModel.PROP_HOME_FOLDER_PROVIDER, defaultHomeFolderProvider); + return properties; + } + + public NodeRef createPerson(Map properties) + { + String userName = DefaultTypeConverter.INSTANCE.convert(String.class, properties + .get(ContentModel.PROP_USERNAME)); + properties.put(ContentModel.PROP_USERNAME, userName); + return nodeService.createNode(getPeopleContainer(), ContentModel.ASSOC_CHILDREN, ContentModel.TYPE_PERSON, + ContentModel.TYPE_PERSON, properties).getChildRef(); + } + + public NodeRef getPeopleContainer() + { + NodeRef rootNodeRef = nodeService.getRootNode(storeRef); + List results = searchService.selectNodes(rootNodeRef, PEOPLE_FOLDER, null, namespacePrefixResolver, + false); + if (results.size() == 0) + { + throw new AlfrescoRuntimeException("Required people system path not found: " + PEOPLE_FOLDER); + } + else + { + return results.get(0); + } + } + + public void deletePerson(String userName) + { + NodeRef personNodeRef = getPersonOrNull(userName); + + // delete the person + if (personNodeRef != null) + { + nodeService.deleteNode(personNodeRef); + } + + // remove user from any containing authorities + Set containerAuthorities = authorityService.getContainingAuthorities(null, userName, true); + for (String containerAuthority : containerAuthorities) + { + authorityService.removeAuthority(containerAuthority, userName); + } + + // remove any user permissions + permissionServiceSPI.deletePermissions(userName); + } + + public Set getAllPeople() + { + SearchParameters sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery("TYPE:\"" + ContentModel.TYPE_PERSON + "\""); + sp.addStore(storeRef); + sp.excludeDataInTheCurrentTransaction(false); + + LinkedHashSet nodes = new LinkedHashSet(); + ResultSet rs = null; + + try + { + rs = searchService.query(sp); + + for (ResultSetRow row : rs) + { + + NodeRef nodeRef = row.getNodeRef(); + if (nodeService.exists(nodeRef)) + { + nodes.add(nodeRef); + } + } + } + finally + { + if (rs != null) + { + rs.close(); + } + } + return nodes; + } + + public void setCreateMissingPeople(boolean createMissingPeople) + { + this.createMissingPeople = createMissingPeople; + } + + public void setNamespacePrefixResolver(NamespacePrefixResolver namespacePrefixResolver) + { + this.namespacePrefixResolver = namespacePrefixResolver; + } + + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + + public void setPermissionServiceSPI(PermissionServiceSPI permissionServiceSPI) + { + this.permissionServiceSPI = permissionServiceSPI; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + public void setStoreUrl(String storeUrl) + { + this.storeRef = new StoreRef(storeUrl); + } + + public String getUserIdentifier(String caseSensitiveUserName) + { + NodeRef nodeRef = getPersonOrNull(caseSensitiveUserName); + if ((nodeRef != null) && nodeService.exists(nodeRef)) + { + String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, + ContentModel.PROP_USERNAME)); + return realUserName; + } + return null; + } + + // IOC Setters + +} diff --git a/source/java/org/alfresco/repo/template/Classification.java b/source/java/org/alfresco/repo/template/Classification.java new file mode 100644 index 0000000000..3c55a5d0f6 --- /dev/null +++ b/source/java/org/alfresco/repo/template/Classification.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.template; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.alfresco.repo.jscript.CategoryTemplateNode; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.alfresco.service.cmr.repository.TemplateNode; +import org.alfresco.service.cmr.search.CategoryService; +import org.alfresco.service.namespace.QName; + +/** + * Support for finding classifications and their root categories. + * + * @author Andy Hind + */ +public final class Classification +{ + private ServiceRegistry services; + private TemplateImageResolver imageResolver; + private StoreRef storeRef; + + public Classification(StoreRef storeRef, ServiceRegistry services, TemplateImageResolver imageResolver) + { + this.storeRef = storeRef; + this.services = services; + this.imageResolver = imageResolver; + } + + /** + * Find all the category nodes in a given classification. + * + * @param aspect + * + * @return all the category nodes in a given classification. + */ + public List getAllCategoryNodes(String aspect) + { + return buildCategoryNodes(services.getCategoryService().getCategories(storeRef, createQName(aspect), + CategoryService.Depth.ANY)); + } + + /** + * Find all the category nodes in a given classification. + * + * @param aspect + * + * @return all the category nodes in a given classification. + */ + public List getAllCategoryNodes(QName aspect) + { + return buildCategoryNodes(services.getCategoryService().getCategories(storeRef, aspect, + CategoryService.Depth.ANY)); + } + + /** + * @return all the aspects that define a classification. + */ + public List getAllClassificationAspects() + { + Collection aspects = services.getCategoryService().getClassificationAspects(); + ArrayList answer = new ArrayList(aspects.size()); + answer.addAll(aspects); + return answer; + } + + /** + * Get the root categories in a classification. + * + * @param aspect + * + * @return List of TemplateNode + */ + public List getRootCategories(String aspect) + { + return buildCategoryNodes(services.getCategoryService().getRootCategories(storeRef, createQName(aspect))); + } + + + private List buildCategoryNodes(Collection cars) + { + ArrayList categoryNodes = new ArrayList(cars.size()); + for (ChildAssociationRef car : cars) + { + categoryNodes.add(new CategoryTemplateNode(car.getChildRef(), this.services, this.imageResolver)); + } + return categoryNodes; + } + + private QName createQName(String s) + { + QName qname; + if (s.indexOf(QName.NAMESPACE_BEGIN) != -1) + { + qname = QName.createQName(s); + } + else + { + qname = QName.createQName(s, this.services.getNamespaceService()); + } + return qname; + } +} diff --git a/source/java/org/alfresco/repo/template/FreeMarkerProcessor.java b/source/java/org/alfresco/repo/template/FreeMarkerProcessor.java index 2ba4994524..6aae071809 100644 --- a/source/java/org/alfresco/repo/template/FreeMarkerProcessor.java +++ b/source/java/org/alfresco/repo/template/FreeMarkerProcessor.java @@ -301,6 +301,13 @@ public class FreeMarkerProcessor implements TemplateProcessor // current date/time is useful to have and isn't supplied by FreeMarker by default model.put("date", new Date()); + // Session support + model.put("session", new Session(services, imageResolver)); + + // Classification support + + model.put("classification", new Classification(companyHome.getStoreRef(), services, imageResolver)); + // add custom method objects model.put("hasAspect", new HasAspectMethod()); model.put("message", new I18NMessageMethod()); diff --git a/source/java/org/alfresco/repo/template/Session.java b/source/java/org/alfresco/repo/template/Session.java new file mode 100644 index 0000000000..e1007de8c9 --- /dev/null +++ b/source/java/org/alfresco/repo/template/Session.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.template; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.TemplateImageResolver; + +/** + * Support session information in free marker templates. + * + * @author Andy Hind + */ +public class Session +{ + + private ServiceRegistry services; + + @SuppressWarnings("unused") + private TemplateImageResolver imageResolver; + + public Session(ServiceRegistry services, TemplateImageResolver imageResolver) + { + this.services = services; + this.imageResolver = imageResolver; + } + + /** + * Get the current authentication ticket. + * + * @return + */ + public String getTicket() + { + return services.getAuthenticationService().getCurrentTicket(); + } +} diff --git a/source/java/org/alfresco/repo/version/common/counter/VersionCounterService.java b/source/java/org/alfresco/repo/version/common/counter/VersionCounterService.java index fa41e2e373..c45aeb35ec 100644 --- a/source/java/org/alfresco/repo/version/common/counter/VersionCounterService.java +++ b/source/java/org/alfresco/repo/version/common/counter/VersionCounterService.java @@ -59,4 +59,16 @@ public interface VersionCounterService * @param storeRef the store reference */ public void resetVersionNumber(StoreRef storeRef); + + /** + * Sets the version number for a specified store. + * + * WARNING: calling this method will completely reset the current + * version count for the specified store and cannot be undone. + * + * @param storeRef the store reference + * @param versionCount the new version count + */ + public void setVersionNumber(StoreRef storeRef, int versionCount); + } diff --git a/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java b/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java index 0755746654..a6c3d20d3c 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java @@ -31,11 +31,10 @@ import org.alfresco.service.cmr.workflow.WorkflowDeployment; import org.alfresco.service.cmr.workflow.WorkflowException; import org.alfresco.service.cmr.workflow.WorkflowService; import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.AbstractLifecycleBean; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.core.io.ClassPathResource; @@ -44,7 +43,7 @@ import org.springframework.core.io.ClassPathResource; * * @author davidc */ -public class WorkflowDeployer implements ApplicationListener +public class WorkflowDeployer extends AbstractLifecycleBean { // Logging support private static Log logger = LogFactory.getLog("org.alfresco.repo.workflow"); @@ -222,16 +221,16 @@ public class WorkflowDeployer implements ApplicationListener } } - /* - * (non-Javadoc) - * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) - */ - public void onApplicationEvent(ApplicationEvent event) + @Override + protected void onBootstrap(ApplicationEvent event) { - if (event instanceof ContextRefreshedEvent) - { - deploy(); - } + deploy(); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + // NOOP } } diff --git a/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java b/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java new file mode 100644 index 0000000000..90eda03c7d --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java @@ -0,0 +1,707 @@ +/* + * Copyright (C) 2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.workflow; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDeployment; +import org.alfresco.service.cmr.workflow.WorkflowInstance; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskState; +import org.alfresco.service.cmr.workflow.WorkflowTransition; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ApplicationContextHelper; +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.ClassPathResource; + +/** + * An interactive console for Workflows. + * + * @author davidc + */ +public class WorkflowInterpreter +{ + // Service dependencies + private WorkflowService workflowService; + private NamespaceService namespaceService; + private PersonService personService; + + /** + * The reader for interaction. + */ + private BufferedReader fIn; + + /** + * Current context + */ + private WorkflowDefinition currentWorkflowDef = null; + private WorkflowPath currentPath = null; + private String currentDeploy = null; + + /** + * Last command issued + */ + private String lastCommand = null; + + /** + * Variables + */ + private Map vars = new HashMap(); + + + /** + * Main entry point. + * + * Syntax: AVMInteractiveConsole storage (new|old). + */ + public static void main(String[] args) + { + ApplicationContext context = ApplicationContextHelper.getApplicationContext(); + WorkflowInterpreter console = (WorkflowInterpreter)context.getBean("workflowInterpreter"); + AuthenticationUtil.setSystemUserAsCurrentUser(); + console.rep(); + System.exit(0); + } + + /** + * Make up a new console. + */ + public WorkflowInterpreter() + { + fIn = new BufferedReader(new InputStreamReader(System.in)); + } + + /** + * @param workflowService The Workflow Service + */ + public void setWorkflowService(WorkflowService workflowService) + { + this.workflowService = workflowService; + } + + /** + * @param namespaceService namespaceService + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * @param personService personService + */ + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + /** + * A Read-Eval-Print loop. + */ + public void rep() + { + while (true) + { + System.out.print("ok> "); + try + { + String line = fIn.readLine(); + if (line.equals("exit")) + { + return; + } + long startms = System.currentTimeMillis(); + System.out.print(interpretCommand(line)); + System.out.println("" + (System.currentTimeMillis() - startms) + "ms"); + } + catch (Exception e) + { + e.printStackTrace(System.err); + System.out.println(""); + } + } + } + + /** + * Interpret a single command using the BufferedReader passed in for any data needed. + * + * @param line The unparsed command + * @return The textual output of the command. + */ + public String interpretCommand(String line) + throws IOException + { + String[] command = line.split(" "); + if (command.length == 0) + { + command = new String[1]; + command[0] = line; + } + + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + + // repeat last command? + if (command[0].equals("r")) + { + if (lastCommand == null) + { + return "No command entered yet."; + } + return "repeating command " + lastCommand + "\n\n" + interpretCommand(lastCommand); + } + + // remember last command + lastCommand = line; + + // execute command + if (command[0].equals("help")) + { + String helpFile = I18NUtil.getMessage("workflow_console.help"); + ClassPathResource helpResource = new ClassPathResource(helpFile); + byte[] helpBytes = new byte[500]; + InputStream helpStream = helpResource.getInputStream(); + try + { + int read = helpStream.read(helpBytes); + while (read != -1) + { + bout.write(helpBytes, 0, read); + read = helpStream.read(helpBytes); + } + } + finally + { + helpStream.close(); + } + } + + else if (command[0].equals("show")) + { + if (command.length < 2) + { + return "Syntax Error.\n"; + } + + else if (command[1].equals("definitions")) + { + List defs = workflowService.getDefinitions(); + for (WorkflowDefinition def : defs) + { + out.println("id: " + def.id + " , name: " + def.name + " , title: " + def.title + " , version: " + def.version); + } + } + + else if (command[1].equals("workflows")) + { + if (currentWorkflowDef == null) + { + return "workflow definition not in use. Enter command use .\n"; + } + List workflows = workflowService.getActiveWorkflows(currentWorkflowDef.id); + for (WorkflowInstance workflow : workflows) + { + out.println("id: " + workflow.id + " , desc: " + workflow.description + " , start date: " + workflow.startDate + " , def: " + workflow.definition.title); + } + } + + else if (command[1].equals("paths")) + { + String workflowId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.instance.id; + if (workflowId == null) + { + return "Syntax Error. Workflow Id not specified.\n"; + } + List paths = workflowService.getWorkflowPaths(workflowId); + for (WorkflowPath path : paths) + { + out.println("path id: " + path.id + " , node: " + path.node.name); + } + } + + else if (command[1].equals("tasks")) + { + String pathId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.id; + if (pathId == null) + { + return "Syntax Error. Path Id not specified.\n"; + } + List tasks = workflowService.getTasksForWorkflowPath(pathId); + for (WorkflowTask task : tasks) + { + out.println("task id: " + task.id + " , name: " + task.name + " , properties: " + task.properties.size()); + } + } + + else if (command[1].equals("transitions")) + { + String workflowId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.instance.id; + if (workflowId == null) + { + return "Syntax Error. Workflow Id not specified.\n"; + } + List paths = workflowService.getWorkflowPaths(workflowId); + for (WorkflowPath path : paths) + { + out.println("path: " + path.id + " , node: " + path.node.name + " , active: " + path.active); + List tasks = workflowService.getTasksForWorkflowPath(path.id); + for (WorkflowTask task : tasks) + { + out.println(" task id: " + task.id + " , name: " + task.name + " , properties: " + task.properties.size()); + } + for (WorkflowTransition transition : path.node.transitions) + { + out.println(" transition id: " + ((transition.id == null || transition.id.equals("")) ? "[default]" : transition.id) + " , title: " + transition.title); + } + } + } + + else if (command[1].equals("my")) + { + if (command.length != 3) + { + return "Syntax Error.\n"; + } + + if (command[2].equals("tasks")) + { + out.println(AuthenticationUtil.getCurrentUserName() + ":"); + List tasks = workflowService.getAssignedTasks(AuthenticationUtil.getCurrentUserName(), WorkflowTaskState.IN_PROGRESS); + for (WorkflowTask task : tasks) + { + out.println("id: " + task.id + " , name: " + task.name + " , properties: " + task.properties.size() + " , workflow: " + task.path.instance.id + " , path: " + task.path.id); + } + } + + else if (command[2].equals("completed")) + { + out.println(AuthenticationUtil.getCurrentUserName() + ":"); + List tasks = workflowService.getAssignedTasks(AuthenticationUtil.getCurrentUserName(), WorkflowTaskState.COMPLETED); + for (WorkflowTask task : tasks) + { + out.println("id: " + task.id + " , name " + task.name + " , properties: " + task.properties.size() + " , workflow: " + task.path.instance.id + " , path: " + task.path.id); + } + } + else + { + return "Syntax Error.\n"; + } + } + else + { + return "Syntax Error.\n"; + } + } + + else if (command[0].equals("desc")) + { + if (command.length < 2) + { + return "Syntax Error.\n"; + } + + if (command[1].equals("task")) + { + if (command.length != 3) + { + return "Syntax Error.\n"; + } + WorkflowTask task = workflowService.getTaskById(command[2]); + out.println("id: " + task.id); + out.println("name: " + task.name); + out.println("title: " + task.title); + out.println("description: " + task.description); + out.println("state: " + task.state); + out.println("path: " + task.path.id); + out.println("transitions: " + task.path.node.transitions.length); + for (WorkflowTransition transition : task.path.node.transitions) + { + out.println(" transition: " + ((transition == null || transition.id.equals("")) ? "[default]" : transition.id) + " , title: " + transition.title + " , desc: " + transition.description); + } + out.println("properties: " + task.properties.size()); + for (Map.Entry prop : task.properties.entrySet()) + { + out.println(" " + prop.getKey() + " = " + prop.getValue()); + } + } + + else if (command[1].equals("workflow")) + { + if (command.length != 3) + { + return "Syntax Error.\n"; + } + WorkflowInstance workflow = workflowService.getWorkflowById(command[2]); + out.println("definition: " + workflow.definition.name); + out.println("id: " + workflow.id); + out.println("description: " + workflow.description); + out.println("active: " + workflow.active); + out.println("start date: " + workflow.startDate); + out.println("end date: " + workflow.endDate); + out.println("initiator: " + workflow.initiator); + out.println("context: " + workflow.context); + out.println("package: " + workflow.workflowPackage); + } + else + { + return "Syntax Error.\n"; + } + } + + else if (command[0].equals("deploy")) + { + if (command.length != 2) + { + return "Syntax Error.\n"; + } + ClassPathResource workflowDef = new ClassPathResource(command[1]); + WorkflowDeployment deployment = workflowService.deployDefinition("jbpm", workflowDef.getInputStream(), MimetypeMap.MIMETYPE_XML); + WorkflowDefinition def = deployment.definition; + for (String problem : deployment.problems) + { + out.println(problem); + } + out.println("deployed definition id: " + def.id + " , name: " + def.name + " , title: " + def.title + " , version: " + def.version); + currentDeploy = command[1]; + out.print(interpretCommand("use " + def.id)); + } + + else if (command[0].equals("redeploy")) + { + if (currentDeploy == null) + { + return "nothing to redeploy\n"; + } + out.print(interpretCommand("deploy " + currentDeploy)); + } + + else if (command[0].equals("use")) + { + if (command.length == 1) + { + out.println("definition: " + ((currentWorkflowDef == null) ? "None" : currentWorkflowDef.id + " , name: " + currentWorkflowDef.title)); + out.println("workflow: " + ((currentPath == null) ? "None" : currentPath.instance.id + " , active: " + currentPath.instance.active)); + out.println("path: " + ((currentPath == null) ? "None" : currentPath.id + " , node: " + currentPath.node.title)); + } + else if (command.length > 1) + { + if (command[1].equals("definition")) + { + if (command.length != 3) + { + return "Syntax Error.\n"; + } + WorkflowDefinition def = workflowService.getDefinitionById(command[2]); + if (def == null) + { + return "Not found.\n"; + } + currentWorkflowDef = def; + currentPath = null; + out.print(interpretCommand("use")); + } + + else if (command[1].equals("workflow")) + { + if (command.length != 3) + { + return "Syntax Error.\n"; + } + WorkflowInstance instance = workflowService.getWorkflowById(command[2]); + currentWorkflowDef = instance.definition; + currentPath = workflowService.getWorkflowPaths(instance.id).get(0); + out.print(interpretCommand("use")); + } + else + { + return "Syntax Error.\n"; + } + } + + } + + else if (command[0].equals("user")) + { + if (command.length == 2) + { + AuthenticationUtil.setCurrentUser(command[1]); + } + out.println("using user " + AuthenticationUtil.getCurrentUserName()); + } + + else if (command[0].equals("start")) + { + Map params = new HashMap(); + for (int i = 1; i < command.length; i++) + { + String[] param = command[i].split("="); + QName qname = QName.createQName(param[0], namespaceService); + if (param.length == 1) + { + if (!vars.containsKey(qname)) + { + return "var " + qname + " not found.\n"; + } + params.put(qname, vars.get(qname)); + } + else if (param.length == 2) + { + params.put(qname, param[1]); + } + else + { + return "Syntax Error.\n"; + } + } + WorkflowPath path = workflowService.startWorkflow(currentWorkflowDef.id, params); + out.println("started workflow id: " + path.instance.id + ", path: " + path.id + " , node: " + path.node.name + " , def: " + path.instance.definition.title); + currentPath = path; + } + + else if (command[0].equals("update")) + { + if (command.length < 3) + { + return "Syntax Error.\n"; + } + + if (command[1].equals("task")) + { + if (command.length < 4) + { + return "Syntax Error.\n"; + } + Map params = new HashMap(); + for (int i = 3; i < command.length; i++) + { + String[] param = command[i].split("="); + QName qname = QName.createQName(param[0], namespaceService); + if (param.length == 1) + { + if (!vars.containsKey(qname)) + { + return "var " + qname + " not found.\n"; + } + params.put(qname, vars.get(qname)); + } + else if (param.length == 2) + { + params.put(qname, param[1]); + } + else + { + return "Syntax Error.\n"; + } + } + WorkflowTask task = workflowService.updateTask(command[2], params, null, null); + out.println("updated task id: " + command[2] + ", properties: " + task.properties.size()); + } + else + { + return "Syntax Error.\n"; + } + } + + else if (command[0].equals("signal")) + { + if (command.length < 2) + { + return "Syntax Error.\n"; + } + WorkflowPath path = workflowService.signal(command[1], (command.length == 3) ? command[2] : null); + out.println("signal sent - path id: " + path.id + " , node: " + path.node.name); + } + + else if (command[0].equals("end")) + { + if (command.length < 3) + { + return "Syntax Error.\n"; + } + if (command[1].equals("task")) + { + WorkflowTask task = workflowService.endTask(command[2], (command.length == 4) ? command[3] : null); + out.println("signal sent - path id: " + task.path.id + " , node: " + task.path.node.name); + } + else if (command[1].equals("workflow")) + { + String workflowId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.instance.id; + if (workflowId == null) + { + return "Syntax Error. Workflow Id not specified.\n"; + } + workflowService.cancelWorkflow(workflowId); + out.println("cancelled workflow" + workflowId); + } + else + { + return "Syntax Error.\n"; + } + } + + else if (command[0].equals("var")) + { + if (command.length == 1) + { + for (Map.Entry entry : vars.entrySet()) + { + out.println(entry.getKey() + " = " + entry.getValue()); + } + } + else if (command.length == 2) + { + String[] param = command[1].split("="); + if (param.length == 0) + { + return "Syntax Error.\n"; + } + if (param.length == 1) + { + QName qname = QName.createQName(param[0], namespaceService); + vars.remove(qname); + out.println("deleted var " + qname); + } + else if (param.length == 2) + { + boolean multi = false; + if (param[0].endsWith("*")) + { + param[0] = param[0].substring(0, param[0].length() -1); + multi = true; + } + QName qname = QName.createQName(param[0], namespaceService); + String[] strValues = param[1].split(","); + if (!multi && strValues.length > 1) + { + return "Syntax Error.\n"; + } + if (!multi) + { + vars.put(qname, strValues[0]); + } + else + { + List values = new ArrayList(); + for (String strValue : strValues) + { + values.add(strValue); + } + vars.put(qname, (Serializable)values); + } + out.println("set var " + qname + " = " + vars.get(qname)); + } + else + { + return "Syntax Error.\n"; + } + } + else if (command.length == 4) + { + if (command[2].equals("person")) + { + boolean multi = false; + if (command[1].endsWith("*")) + { + command[1] = command[1].substring(0, command[1].length() -1); + multi = true; + } + QName qname = QName.createQName(command[1], namespaceService); + String[] strValues = command[3].split(","); + if (!multi && strValues.length > 1) + { + return "Syntax Error.\n"; + } + if (!multi) + { + NodeRef auth = personService.getPerson(strValues[0]); + vars.put(qname, auth); + } + else + { + List values = new ArrayList(); + for (String strValue : strValues) + { + NodeRef auth = personService.getPerson(strValue); + values.add(auth); + } + vars.put(qname, (Serializable)values); + } + out.println("set var " + qname + " = " + vars.get(qname)); + } + else + { + return "Syntax Error.\n"; + } + } + else + { + return "Syntax Error.\n"; + } + } + + else + { + return "Syntax Error.\n"; + } + + out.flush(); + String retVal = new String(bout.toByteArray()); + out.close(); + return retVal; + } + + + /** + * Get currently used workflow definition + * + * @return workflow definition + */ + public WorkflowDefinition getCurrentWorkflowDef() + { + return currentWorkflowDef; + } + + /** + * Get current user name + * + * @return user name + */ + public String getCurrentUserName() + { + return AuthenticationUtil.getCurrentUserName(); + } + +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/workflow/WorkflowPackageImpl.java b/source/java/org/alfresco/repo/workflow/WorkflowPackageImpl.java index dc8737b20d..f0dca684f4 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowPackageImpl.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowPackageImpl.java @@ -25,6 +25,7 @@ import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.workflow.WorkflowException; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; @@ -47,6 +48,7 @@ public class WorkflowPackageImpl implements WorkflowPackageComponent private SearchService searchService; private NodeService nodeService; private NamespaceService namespaceService; + private PermissionService permissionService; private NodeRef systemWorkflowContainer = null; @@ -74,6 +76,11 @@ public class WorkflowPackageImpl implements WorkflowPackageComponent this.nodeService = nodeService; } + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + /** * @param namespaceService namespace service */ @@ -113,6 +120,8 @@ public class WorkflowPackageImpl implements WorkflowPackageComponent QName qname = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, containerName); ChildAssociationRef childRef = nodeService.createNode(packages, ContentModel.ASSOC_CONTAINS, qname, ContentModel.TYPE_SYSTEM_FOLDER); container = childRef.getChildRef(); + // TODO: For now, grant full access to everyone + permissionService.setPermission(container, PermissionService.ALL_AUTHORITIES, PermissionService.ALL_PERMISSIONS, true); isSystemPackage = true; } diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java index 9e1cfbabf0..74a293728f 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java @@ -311,10 +311,25 @@ public class JBPMEngine extends BPMEngine /* (non-Javadoc) * @see org.alfresco.repo.workflow.WorkflowDefinitionComponent#getDefinitionById(java.lang.String) */ - public WorkflowDefinition getDefinitionById(String workflowDefinitionId) + public WorkflowDefinition getDefinitionById(final String workflowDefinitionId) { - // TODO - throw new UnsupportedOperationException(); + try + { + return (WorkflowDefinition)jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + // retrieve process + GraphSession graphSession = context.getGraphSession(); + ProcessDefinition processDefinition = graphSession.getProcessDefinition(getJbpmId(workflowDefinitionId)); + return processDefinition == null ? null : createWorkflowDefinition(processDefinition); + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to retrieve workflow definition '" + workflowDefinitionId + "'", e); + } } /* (non-Javadoc) @@ -1714,7 +1729,7 @@ public class JBPMEngine extends BPMEngine workflowTransition.id = transition.getName(); Node node = transition.getFrom(); workflowTransition.isDefault = node.getDefaultLeavingTransition().equals(transition); - if (workflowTransition.id.length() == 0) + if (workflowTransition.id == null || workflowTransition.id.length() == 0) { workflowTransition.title = getLabel(DEFAULT_TRANSITION_LABEL, TITLE_LABEL, workflowTransition.id); workflowTransition.description = getLabel(DEFAULT_TRANSITION_LABEL, DESC_LABEL, workflowTransition.title); diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java index 3e927d4bba..47c9465e0d 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java @@ -341,8 +341,10 @@ public class JBPMEngineTest extends BaseSpringTest public void testSignal() { + Map parameters = new HashMap(); + parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "testNode"), testNodeRef); WorkflowDefinition workflowDef = getTestDefinition(); - WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, null); + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, parameters); assertNotNull(path); WorkflowPath updatedPath = workflowComponent.signal(path.id, path.node.transitions[1].id); assertNotNull(updatedPath); @@ -374,6 +376,26 @@ public class JBPMEngineTest extends BaseSpringTest } + public void xtestMultiAssign() + { + WorkflowDefinition workflowDef = getTestDefinition(); + List bpm_assignees = new ArrayList(); + bpm_assignees.add("admin"); + bpm_assignees.add("bob"); + bpm_assignees.add("fred"); + Map parameters = new HashMap(); + parameters.put(QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "assignees"), (Serializable)bpm_assignees); + parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "testNode"), testNodeRef); + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, parameters); + assertNotNull(path); + List tasks = workflowComponent.getTasksForWorkflowPath(path.id); + assertNotNull(tasks); + assertEquals(1, tasks.size()); + WorkflowTask updatedTask = taskComponent.endTask(tasks.get(0).id, "multi"); + assertNotNull(updatedTask); + } + + public void testEndTask() { WorkflowDefinition workflowDef = getTestDefinition(); diff --git a/source/java/org/alfresco/service/cmr/action/ActionService.java b/source/java/org/alfresco/service/cmr/action/ActionService.java index 171051fc94..b34b500e8f 100644 --- a/source/java/org/alfresco/service/cmr/action/ActionService.java +++ b/source/java/org/alfresco/service/cmr/action/ActionService.java @@ -202,7 +202,7 @@ public interface ActionService * @param nodeRef the node reference * @param action the action */ - @Auditable(key = Auditable.Key.ARG_1, parameters = {"nodeRef", "action" }) + @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef", "action" }) void saveAction(NodeRef nodeRef, Action action); /** @@ -211,7 +211,7 @@ public interface ActionService * @param nodeRef the node reference * @return the list of actions */ - @Auditable(key = Auditable.Key.ARG_1, parameters = {"nodeRef"}) + @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef"}) List getActions(NodeRef nodeRef); /** diff --git a/source/java/org/alfresco/service/cmr/repository/CopyService.java b/source/java/org/alfresco/service/cmr/repository/CopyService.java index d69e77e82a..857fb7431a 100644 --- a/source/java/org/alfresco/service/cmr/repository/CopyService.java +++ b/source/java/org/alfresco/service/cmr/repository/CopyService.java @@ -74,6 +74,7 @@ public interface CopyService * @param destinationAssocTypeQName the type of the new child assoc * @param destinationQName the qualified name of the child association from the * parent to the new node + * @param copyChildren indicates that the children of the node should also be copied * * @return the new node reference */ @@ -85,10 +86,32 @@ public interface CopyService QName destinationQName, boolean copyChildren); + /** + * @see CopyService#copy(NodeRef, NodeRef, QName, QName, boolean) + * + * Ensures the copy name is the same as the origional or is renamed to prevent duplicate names. + * + * @param sourceNodeRef the node reference used as the source of the copy + * @param destinationParent the intended parent of the new node + * @param destinationAssocTypeQName the type of the new child assoc + * @param destinationQName the qualified name of the child association from the + * parent to the new node + * @param copyChildren indicates that the children of the node should also be copied + * + * @return the new node reference + */ + @Auditable(key = Auditable.Key.ARG_0, parameters = {"sourceNodeRef", "destinationParent", "destinationAssocTypeQName", "destinationQName", "copyChildren"}) + public NodeRef copyAndRename( + NodeRef sourceNodeRef, + NodeRef destinationParent, + QName destinationAssocTypeQName, + QName destinationQName, + boolean copyChildren); + /** * By default children of the source node are not copied. * - * @see NodeCopyService#copy(NodeRef, NodeRef, QName, QName, boolean) + * @see CopyService#copy(NodeRef, NodeRef, QName, QName, boolean) * * @param sourceNodeRef the node reference used as the source of the copy * @param destinationParent the intended parent of the new node @@ -142,4 +165,5 @@ public interface CopyService */ @Auditable(key = Auditable.Key.ARG_0, parameters = {"nodeRef"}) public List getCopies(NodeRef nodeRef); + } diff --git a/source/java/org/alfresco/service/cmr/repository/ScriptImplementation.java b/source/java/org/alfresco/service/cmr/repository/ScriptImplementation.java new file mode 100644 index 0000000000..cb8353abba --- /dev/null +++ b/source/java/org/alfresco/service/cmr/repository/ScriptImplementation.java @@ -0,0 +1,20 @@ +/** + * + */ +package org.alfresco.service.cmr.repository; + +/** + * Interface to represent a server side script implementation + * + * @author Roy Wetherall + * + */ +public interface ScriptImplementation +{ + /** + * Returns the name of the script + * + * @return the name of the script + */ + String getScriptName(); +} diff --git a/source/java/org/alfresco/service/cmr/repository/ScriptService.java b/source/java/org/alfresco/service/cmr/repository/ScriptService.java index ad742600f5..80112477f7 100644 --- a/source/java/org/alfresco/service/cmr/repository/ScriptService.java +++ b/source/java/org/alfresco/service/cmr/repository/ScriptService.java @@ -83,4 +83,12 @@ public interface ScriptService @Auditable(parameters = {"script", "model"}) public Object executeScriptString(String script, Map model) throws ScriptException; + + /** + * Registers a script implementation with the script service + * + * @param script the script implementation + */ + @Auditable(parameters = {"script"}) + public void registerScript(ScriptImplementation script); } diff --git a/source/java/org/alfresco/service/cmr/repository/TemplateNode.java b/source/java/org/alfresco/service/cmr/repository/TemplateNode.java index 6d2a05b55c..bd3ce6bea0 100644 --- a/source/java/org/alfresco/service/cmr/repository/TemplateNode.java +++ b/source/java/org/alfresco/service/cmr/repository/TemplateNode.java @@ -60,7 +60,7 @@ import freemarker.ext.dom.NodeModel; * * @author Kevin Roast */ -public final class TemplateNode implements Serializable +public class TemplateNode implements Serializable { private static final long serialVersionUID = 1234390333739034171L; @@ -78,7 +78,7 @@ public final class TemplateNode implements Serializable private Map> assocs = null; /** Cached values */ - private NodeRef nodeRef; + protected NodeRef nodeRef; private String name; private QName type; private String path; @@ -87,15 +87,16 @@ public final class TemplateNode implements Serializable private QNameMap properties; private List permissions = null; private boolean propsRetrieved = false; - private ServiceRegistry services = null; + protected ServiceRegistry services = null; private Boolean isDocument = null; private Boolean isContainer = null; private String displayPath = null; private String mimetype = null; private Long size = null; - private TemplateImageResolver imageResolver = null; + protected TemplateImageResolver imageResolver = null; private TemplateNode parent = null; private ChildAssociationRef primaryParentAssoc = null; + private Boolean isCategory = null; // ------------------------------------------------------------------------------ @@ -360,6 +361,20 @@ public final class TemplateNode implements Serializable return locked; } + /** + * @return true if the node is a Category instance + */ + public boolean getIsCategory() + { + if (isCategory == null) + { + DictionaryService dd = this.services.getDictionaryService(); + isCategory = Boolean.valueOf(dd.isSubClass(getType(), ContentModel.TYPE_CATEGORY)); + } + + return isCategory.booleanValue(); + } + /** * @return the parent node */ @@ -482,17 +497,17 @@ public final class TemplateNode implements Serializable */ public NodeModel getXmlNodeModel() { - try - { - return NodeModel.parse(new InputSource(new StringReader(getContent()))); - } - catch (Throwable err) - { - if (logger.isDebugEnabled()) - logger.debug(err.getMessage(), err); - - return null; - } + try + { + return NodeModel.parse(new InputSource(new StringReader(getContent()))); + } + catch (Throwable err) + { + if (logger.isDebugEnabled()) + logger.debug(err.getMessage(), err); + + return null; + } } /** @@ -661,14 +676,18 @@ public final class TemplateNode implements Serializable } + // ------------------------------------------------------------------------------ // Audit API - + /** + * @return a list of AuditInfo objects describing the Audit Trail for this node instance + */ public List getAuditTrail() { return this.services.getAuditService().getAuditTrail(this.nodeRef); } + // ------------------------------------------------------------------------------ // Misc helpers