diff --git a/config/alfresco/action-services-context.xml b/config/alfresco/action-services-context.xml index bf78e2427a..05a962a69f 100644 --- a/config/alfresco/action-services-context.xml +++ b/config/alfresco/action-services-context.xml @@ -442,8 +442,8 @@ - - + + false diff --git a/config/alfresco/application-context-core.xml b/config/alfresco/application-context-core.xml index 30665d99cb..ec66e257a1 100644 --- a/config/alfresco/application-context-core.xml +++ b/config/alfresco/application-context-core.xml @@ -15,7 +15,6 @@ - diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index b6c6afc6ab..75f88ee3cf 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -90,6 +90,7 @@ classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoPostCreate-JBPM-Extra.sql + classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoPostCreate-JBPM-FK-indexes.sql @@ -136,6 +137,8 @@ + + @@ -524,6 +527,21 @@ + + + + + + contentDiskDriver + + + + + org.alfresco.jlan.server.filesys.DiskInterface + + + + diff --git a/config/alfresco/bootstrap/webscripts/folder.get.atom.ftl b/config/alfresco/bootstrap/webscripts/folder.get.atom.ftl index 098fe81dfa..d1df69f7d2 100644 --- a/config/alfresco/bootstrap/webscripts/folder.get.atom.ftl +++ b/config/alfresco/bootstrap/webscripts/folder.get.atom.ftl @@ -1,12 +1,12 @@ Alfresco (${server.edition}) - Folder: ${folder.displayPath}/${folder.name} + Folder: ${folder.displayPath}/${folder.name?xml} ${xmldate(date)} ${absurl(url.context)}/images/logo/AlfrescoLogo16.ico <#list folder.children as child> - ${child.name} + ${child.name?xml} <#if child.isContainer> <#else> @@ -15,10 +15,10 @@ ${absurl(url.context)}${child.icon16} urn:uuid:${child.id} ${xmldate(child.properties.modified)} - ${child.properties.description!""} + ${child.properties.description?xml!""} - ${child.properties.creator} - + ${child.properties.creator?xml} + diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 89ed40faf0..3a847ae50e 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -83,6 +83,10 @@ SYSTEM_PROPERTIES_MODE_NEVER + + + 6 + @@ -112,7 +116,7 @@ ${alfresco.jgroups.bind_address} - + ${alfresco.jgroups.bind_interface} @@ -459,6 +463,7 @@ alfresco.messages.content-filter-languages alfresco.messages.jbpm-engine-messages alfresco.messages.activiti-engine-messages + alfresco.messages.wdr-messages @@ -1033,12 +1038,18 @@ + + + + + + - opaquelocktoken - sharedLockTokens - lockDepth - lockScope + webdav:opaquelocktoken + webdav:sharedLockTokens + webdav:lockDepth + webdav:lockScope diff --git a/config/alfresco/dao/dao-context.xml b/config/alfresco/dao/dao-context.xml index 9bae33a44e..95721aa27f 100644 --- a/config/alfresco/dao/dao-context.xml +++ b/config/alfresco/dao/dao-context.xml @@ -60,6 +60,8 @@ + + diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.Dialect/AlfrescoPostCreate-JBPM-FK-indexes.sql b/config/alfresco/dbscripts/create/org.hibernate.dialect.Dialect/AlfrescoPostCreate-JBPM-FK-indexes.sql new file mode 100644 index 0000000000..c33e74115a --- /dev/null +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.Dialect/AlfrescoPostCreate-JBPM-FK-indexes.sql @@ -0,0 +1,109 @@ +-- +-- Title: Upgrade to V3.4 - Add indexes for jbpm foreign keys +-- Database: Generic +-- Since: V3.4 schema 4204 +-- Author: pavelyur +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +CREATE INDEX FK_ACTION_REFACT ON JBPM_ACTION (REFERENCEDACTION_); --(optional) +CREATE INDEX FK_CRTETIMERACT_TA ON JBPM_ACTION (TIMERACTION_); --(optional) +CREATE INDEX FK_ACTION_PROCDEF ON JBPM_ACTION (PROCESSDEFINITION_); --(optional) +CREATE INDEX FK_ACTION_EVENT ON JBPM_ACTION (EVENT_); --(optional) +CREATE INDEX FK_ACTION_ACTNDEL ON JBPM_ACTION (ACTIONDELEGATION_); --(optional) +CREATE INDEX FK_ACTION_EXPTHDL ON JBPM_ACTION(EXCEPTIONHANDLER_); --(optional) +CREATE INDEX FK_BYTEARR_FILDEF ON JBPM_BYTEARRAY (FILEDEFINITION_); --(optional) +CREATE INDEX FK_BYTEBLOCK_FILE ON JBPM_BYTEBLOCK (PROCESSFILE_); --(optional) +CREATE INDEX FK_COMMENT_TOKEN ON JBPM_COMMENT (TOKEN_); --(optional) +CREATE INDEX FK_COMMENT_TSK ON JBPM_COMMENT (TASKINSTANCE_); --(optional) +CREATE INDEX FK_DECCOND_DEC ON JBPM_DECISIONCONDITIONS (DECISION_); --(optional) +CREATE INDEX FK_DELEGATION_PRCD ON JBPM_DELEGATION (PROCESSDEFINITION_); --(optional) +CREATE INDEX FK_EVENT_PROCDEF ON JBPM_EVENT (PROCESSDEFINITION_); --(optional) +CREATE INDEX FK_EVENT_TRANS ON JBPM_EVENT (TRANSITION_); --(optional) +CREATE INDEX FK_EVENT_NODE ON JBPM_EVENT (NODE_); --(optional) +CREATE INDEX FK_EVENT_TASK ON JBPM_EVENT (TASK_); --(optional) +CREATE INDEX FK_JOB_PRINST ON JBPM_JOB (PROCESSINSTANCE_); --(optional) +CREATE INDEX FK_JOB_ACTION ON JBPM_JOB (ACTION_); --(optional) +CREATE INDEX FK_JOB_TOKEN ON JBPM_JOB (TOKEN_); --(optional) +CREATE INDEX FK_JOB_NODE ON JBPM_JOB (NODE_); --(optional) +CREATE INDEX FK_JOB_TSKINST ON JBPM_JOB (TASKINSTANCE_); --(optional) +CREATE INDEX FK_LOG_SOURCENODE ON JBPM_LOG (SOURCENODE_); --(optional) +CREATE INDEX FK_LOG_DESTNODE ON JBPM_LOG (DESTINATIONNODE_); --(optional) +CREATE INDEX FK_LOG_TOKEN ON JBPM_LOG (TOKEN_); --(optional) +CREATE INDEX FK_LOG_TRANSITION ON JBPM_LOG (TRANSITION_); --(optional) +CREATE INDEX FK_LOG_TASKINST ON JBPM_LOG (TASKINSTANCE_); --(optional) +CREATE INDEX FK_LOG_CHILDTOKEN ON JBPM_LOG (CHILD_); --(optional) +CREATE INDEX FK_LOG_OLDBYTES ON JBPM_LOG (OLDBYTEARRAY_); --(optional) +CREATE INDEX FK_LOG_SWIMINST ON JBPM_LOG (SWIMLANEINSTANCE_); --(optional) +CREATE INDEX FK_LOG_NEWBYTES ON JBPM_LOG (NEWBYTEARRAY_); --(optional) +CREATE INDEX FK_LOG_ACTION ON JBPM_LOG (ACTION_); --(optional) +CREATE INDEX FK_LOG_VARINST ON JBPM_LOG (VARIABLEINSTANCE_); --(optional) +CREATE INDEX FK_LOG_NODE ON JBPM_LOG (NODE_); --(optional) +CREATE INDEX FK_LOG_PARENT ON JBPM_LOG (PARENT_); --(optional) +CREATE INDEX FK_MODDEF_PROCDEF ON JBPM_MODULEDEFINITION (PROCESSDEFINITION_); --(optional) +CREATE INDEX FK_TSKDEF_START ON JBPM_MODULEDEFINITION (STARTTASK_); --(optional) +CREATE INDEX FK_MODINST_PRCINST ON JBPM_MODULEINSTANCE (PROCESSINSTANCE_); --(optional) +CREATE INDEX FK_TASKMGTINST_TMD ON JBPM_MODULEINSTANCE (TASKMGMTDEFINITION_); --(optional) +CREATE INDEX FK_DECISION_DELEG ON JBPM_NODE (DECISIONDELEGATION); --(optional) +CREATE INDEX FK_NODE_PROCDEF ON JBPM_NODE (PROCESSDEFINITION_); --(optional) +CREATE INDEX FK_NODE_ACTION ON JBPM_NODE (ACTION_); --(optional) +CREATE INDEX FK_PROCST_SBPRCDEF ON JBPM_NODE (SUBPROCESSDEFINITION_); --(optional) +CREATE INDEX FK_NODE_SCRIPT ON JBPM_NODE (SCRIPT_); --(optional) +CREATE INDEX FK_NODE_SUPERSTATE ON JBPM_NODE (SUPERSTATE_); --(optional) +CREATE INDEX FK_POOLEDACTOR_SLI ON JBPM_POOLEDACTOR (SWIMLANEINSTANCE_); --(optional) +CREATE INDEX FK_PROCDEF_STRTSTA ON JBPM_PROCESSDEFINITION (STARTSTATE_); --(optional) +CREATE INDEX FK_PROCIN_PROCDEF ON JBPM_PROCESSINSTANCE (PROCESSDEFINITION_); --(optional) +CREATE INDEX FK_PROCIN_ROOTTKN ON JBPM_PROCESSINSTANCE (ROOTTOKEN_); --(optional) +CREATE INDEX FK_PROCIN_SPROCTKN ON JBPM_PROCESSINSTANCE (SUPERPROCESSTOKEN_); --(optional) +CREATE INDEX FK_RTACTN_PROCINST ON JBPM_RUNTIMEACTION (PROCESSINSTANCE_); --(optional) +CREATE INDEX FK_RTACTN_ACTION ON JBPM_RUNTIMEACTION (ACTION_); --(optional) +CREATE INDEX FK_SWL_ASSDEL ON JBPM_SWIMLANE (ASSIGNMENTDELEGATION_); --(optional) +CREATE INDEX FK_SWL_TSKMGMTDEF ON JBPM_SWIMLANE (TASKMGMTDEFINITION_); --(optional) +CREATE INDEX FK_SWIMLANEINST_TM ON JBPM_SWIMLANEINSTANCE (TASKMGMTINSTANCE_); --(optional) +CREATE INDEX FK_SWIMLANEINST_SL ON JBPM_SWIMLANEINSTANCE (SWIMLANE_); --(optional) +CREATE INDEX FK_TASK_STARTST ON JBPM_TASK (STARTSTATE_); --(optional) +CREATE INDEX FK_TASK_PROCDEF ON JBPM_TASK (PROCESSDEFINITION_); --(optional) +CREATE INDEX FK_TASK_ASSDEL ON JBPM_TASK (ASSIGNMENTDELEGATION_); --(optional) +CREATE INDEX FK_TASK_SWIMLANE ON JBPM_TASK (SWIMLANE_); --(optional) +CREATE INDEX FK_TASK_TASKNODE ON JBPM_TASK (TASKNODE_); --(optional) +CREATE INDEX FK_TASK_TASKMGTDEF ON JBPM_TASK (TASKMGMTDEFINITION_); --(optional) +CREATE INDEX FK_TSK_TSKCTRL ON JBPM_TASK (TASKCONTROLLER_); --(optional) +CREATE INDEX FK_TASKACTPL_TSKI ON JBPM_TASKACTORPOOL (TASKINSTANCE_); --(optional) +CREATE INDEX FK_TSKACTPOL_PLACT ON JBPM_TASKACTORPOOL (POOLEDACTOR_); --(optional) +CREATE INDEX FK_TSKCTRL_DELEG ON JBPM_TASKCONTROLLER (TASKCONTROLLERDELEGATION_); --(optional) +CREATE INDEX FK_TSKINS_PRCINS ON JBPM_TASKINSTANCE (PROCINST_); --(optional) +CREATE INDEX FK_TASKINST_TMINST ON JBPM_TASKINSTANCE (TASKMGMTINSTANCE_); --(optional) +CREATE INDEX FK_TASKINST_TOKEN ON JBPM_TASKINSTANCE (TOKEN_); --(optional) +CREATE INDEX FK_TASKINST_SLINST ON JBPM_TASKINSTANCE (SWIMLANINSTANCE_); --(optional) +CREATE INDEX FK_TASKINST_TASK ON JBPM_TASKINSTANCE (TASK_); --(optional) +CREATE INDEX FK_TOKEN_SUBPI ON JBPM_TOKEN (SUBPROCESSINSTANCE_); --(optional) +CREATE INDEX FK_TOKEN_PROCINST ON JBPM_TOKEN (PROCESSINSTANCE_); --(optional) +CREATE INDEX FK_TOKEN_NODE ON JBPM_TOKEN (NODE_); --(optional) +CREATE INDEX FK_TOKEN_PARENT ON JBPM_TOKEN (PARENT_); --(optional) +CREATE INDEX FK_TKVARMAP_TOKEN ON JBPM_TOKENVARIABLEMAP (TOKEN_); --(optional) +CREATE INDEX FK_TKVARMAP_CTXT ON JBPM_TOKENVARIABLEMAP (CONTEXTINSTANCE_); --(optional) +CREATE INDEX FK_TRANSITION_FROM ON JBPM_TRANSITION (FROM_); --(optional) +CREATE INDEX FK_TRANS_PROCDEF ON JBPM_TRANSITION (PROCESSDEFINITION_); --(optional) +CREATE INDEX FK_TRANSITION_TO ON JBPM_TRANSITION (TO_); --(optional) +CREATE INDEX FK_VARACC_PROCST ON JBPM_VARIABLEACCESS (PROCESSSTATE_); --(optional) +CREATE INDEX FK_VARACC_SCRIPT ON JBPM_VARIABLEACCESS (SCRIPT_); --(optional) +CREATE INDEX FK_VARACC_TSKCTRL ON JBPM_VARIABLEACCESS (TASKCONTROLLER_); --(optional) +CREATE INDEX FK_VARINST_PRCINST ON JBPM_VARIABLEINSTANCE (PROCESSINSTANCE_); --(optional) +CREATE INDEX FK_VARINST_TKVARMP ON JBPM_VARIABLEINSTANCE (TOKENVARIABLEMAP_); --(optional) +CREATE INDEX FK_VARINST_TK ON JBPM_VARIABLEINSTANCE (TOKEN_); --(optional) +CREATE INDEX FK_BYTEINST_ARRAY ON JBPM_VARIABLEINSTANCE (BYTEARRAYVALUE_); --(optional) +CREATE INDEX FK_VAR_TSKINST ON JBPM_VARIABLEINSTANCE (TASKINSTANCE_); --(optional) + + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V3.4-JBPM-FK-indexes'; +INSERT INTO alf_applied_patch + (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) + VALUES + ( + 'patch.db-V3.4-JBPM-FK-indexes', 'Manually executed script upgrade to add FK indexes for JBPM', + 0, 4305, -1, 4306, null, 'UNKOWN', ${TRUE}, ${TRUE}, 'Script completed' + ); diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoPostCreate-JBPM-FK-indexes.sql b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoPostCreate-JBPM-FK-indexes.sql new file mode 100644 index 0000000000..3df4d6fd47 --- /dev/null +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoPostCreate-JBPM-FK-indexes.sql @@ -0,0 +1,22 @@ +-- +-- Title: Upgrade to V3.4 - Add indexes for jbpm foreign keys +-- Database: MySQL +-- Since: V3.4 schema 4204 +-- Author: pavelyur +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +-- do nothing for mysql + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V3.4-JBPM-FK-indexes'; +INSERT INTO alf_applied_patch + (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) + VALUES + ( + 'patch.db-V3.4-JBPM-FK-indexes', 'Manually executed script upgrade to add FK indexes for JBPM', + 0, 4305, -1, 4306, null, 'UNKOWN', ${TRUE}, ${TRUE}, 'Script completed' + ); diff --git a/config/alfresco/dbscripts/upgrade/3.4/org.hibernate.dialect.Dialect/AVM-rename-dupes.sql b/config/alfresco/dbscripts/upgrade/3.4/org.hibernate.dialect.Dialect/AVM-rename-dupes.sql index d3993eb90f..1e90dfaee7 100644 --- a/config/alfresco/dbscripts/upgrade/3.4/org.hibernate.dialect.Dialect/AVM-rename-dupes.sql +++ b/config/alfresco/dbscripts/upgrade/3.4/org.hibernate.dialect.Dialect/AVM-rename-dupes.sql @@ -27,17 +27,17 @@ FROM LOWER(ce.name) = entities.lname AND ce.child_id != entities.max_child_id; -UPDATE avm_child_entries - SET name = name || '-renamed.duplicate.mark-' || child_id || '.temp' +UPDATE avm_child_entries ce + SET name = ce.name || '-renamed.duplicate.mark-' || ce.child_id || '.temp' WHERE EXISTS (SELECT 1 FROM avm_tmp_child_entries tmp WHERE - parent_id = tmp.parent_id AND - name = tmp.name AND - child_id = tmp.child_id); + ce.parent_id = tmp.parent_id AND + ce.name = tmp.name AND + ce.child_id = tmp.child_id); --ASSIGN:update_count=value SELECT diff --git a/config/alfresco/dbscripts/upgrade/3.4/org.hibernate.dialect.Dialect/varchar-field-sizes-quadruple-increasing.sql b/config/alfresco/dbscripts/upgrade/3.4/org.hibernate.dialect.Dialect/varchar-field-sizes-quadruple-increasing.sql new file mode 100644 index 0000000000..29bd0ec2e6 --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/3.4/org.hibernate.dialect.Dialect/varchar-field-sizes-quadruple-increasing.sql @@ -0,0 +1,21 @@ +-- +-- Title: Increasing 'VARCHAR' field sizes quadruply for DB2 dialect +-- Database: Generic +-- Since: V3.4 +-- Author: Dmitry Velichkevich +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- +-- ALF-4300: DB2: Review schema (eg. VARCHAR columns) with respect to multi-byte support (when using DB2 / UTF-8) + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V3.4-VarcharFieldSizesQuadrupleIncreasing'; +INSERT INTO + alf_applied_patch + (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) +VALUES ( + 'patch.db-V3.4-VarcharFieldSizesQuadrupleIncreasing', 'Increasing VARCHAR field sizes quadruply for DB2 dialect V3.4', + 0, 4303, -1, 4304, null, 'UNKOWN', ${TRUE}, ${TRUE}, 'Script completed' +); diff --git a/config/alfresco/form-services-context.xml b/config/alfresco/form-services-context.xml index c65d96e587..09043d09e8 100644 --- a/config/alfresco/form-services-context.xml +++ b/config/alfresco/form-services-context.xml @@ -159,9 +159,8 @@ parent="baseFormProcessor"> - - workflow - + + - - task - + + diff --git a/config/alfresco/hibernate-context.xml b/config/alfresco/hibernate-context.xml index 6b7f56290c..b7122deeef 100644 --- a/config/alfresco/hibernate-context.xml +++ b/config/alfresco/hibernate-context.xml @@ -67,6 +67,7 @@ org/alfresco/repo/workflow/jbpm/jbpm.Join.hbm.xml org/jbpm/graph/node/State.hbm.xml org/jbpm/graph/node/TaskNode.hbm.xml + org/alfresco/repo/workflow/jbpm/jbpm.TaskNode.hbm.xml org/jbpm/context/def/ContextDefinition.hbm.xml org/jbpm/context/def/VariableAccess.hbm.xml org/jbpm/taskmgmt/def/TaskMgmtDefinition.hbm.xml @@ -98,6 +99,7 @@ org/jbpm/job/Timer.hbm.xml org/alfresco/repo/workflow/jbpm/jbpm.Timer.hbm.xml org/jbpm/job/ExecuteNodeJob.hbm.xml + org/alfresco/repo/workflow/jbpm/jbpm.ExecuteNodeJob.hbm.xml org/jbpm/job/ExecuteActionJob.hbm.xml org/jbpm/taskmgmt/exe/TaskMgmtInstance.hbm.xml org/jbpm/taskmgmt/exe/TaskInstance.hbm.xml diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/patch-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/patch-common-SqlMap.xml index 87273d4cea..6178837e5d 100644 --- a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/patch-common-SqlMap.xml +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/patch-common-SqlMap.xml @@ -79,6 +79,21 @@ + + + + + + + + + + + + + + + @@ -359,6 +374,138 @@ and (lower(ce.name) = entities.lname) order by parent_id, lower(name), child_id + ]]> + + + + + + + + + + + diff --git a/config/alfresco/invitation-service-context.xml b/config/alfresco/invitation-service-context.xml index 7d4bf14161..4cf49594c5 100644 --- a/config/alfresco/invitation-service-context.xml +++ b/config/alfresco/invitation-service-context.xml @@ -10,6 +10,7 @@ + @@ -70,4 +71,4 @@ - \ No newline at end of file + diff --git a/config/alfresco/jbpm-context.xml b/config/alfresco/jbpm-context.xml index 4c029ab307..96287e2982 100644 --- a/config/alfresco/jbpm-context.xml +++ b/config/alfresco/jbpm-context.xml @@ -6,7 +6,7 @@ - + + diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 9bb96341f4..02b637ca89 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -1878,7 +1878,7 @@ - + @@ -2760,4 +2760,48 @@ + + + + + + + + + classpath:alfresco/dbscripts/upgrade/3.4/${db.script.dialect}/varchar-field-sizes-quadruple-increasing.sql + + + + + patch.fixAclInheritance + patch.fixAclInheritance.description + 0 + 4304 + 4305 + + + + + + + + + + + + + + + + + patch.db-V3.4-JBPM-FK-indexes + patch.schemaUpgradeScript.description + 0 + 4305 + 4306 + + classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoPostCreate-JBPM-FK-indexes.sql + + + diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml index 7d80d56b5f..1f05ff018d 100644 --- a/config/alfresco/public-services-security-context.xml +++ b/config/alfresco/public-services-security-context.xml @@ -421,7 +421,7 @@ org.alfresco.service.cmr.model.FileFolderService.rename=ACL_NODE.0.sys:base.WriteProperties org.alfresco.service.cmr.model.FileFolderService.move=ACL_NODE.0.sys:base.DeleteNode,ACL_NODE.1.sys:base.CreateChildren org.alfresco.service.cmr.model.FileFolderService.moveFrom=ACL_NODE.0.sys:base.DeleteNode,ACL_NODE.2.sys:base.CreateChildren - org.alfresco.service.cmr.model.FileFolderService.copy=ACL_NODE.0.sys:base.ReadProperties,ACL_NODE.1.sys:base.CreateChildren + org.alfresco.service.cmr.model.FileFolderService.copy=ACL_NODE.0.sys:base.Read,ACL_NODE.1.sys:base.CreateChildren org.alfresco.service.cmr.model.FileFolderService.create=ACL_NODE.0.sys:base.CreateChildren org.alfresco.service.cmr.model.FileFolderService.delete=ACL_NODE.0.sys:base.DeleteNode org.alfresco.service.cmr.model.FileFolderService.getNamePath=ACL_NODE.1.sys:base.ReadProperties diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 234d9d21ff..224c4efef5 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -39,6 +39,9 @@ system.webdav.rootPath=${protocols.rootPath} # servlet allows unauthenticated deployment of new workflows. system.workflow.deployservlet.enabled=false +# Sets the location for the JBPM Configuration File +system.workflow.jbpm.config.location=classpath:org/alfresco/repo/workflow/jbpm/jbpm.cfg.xml + # ######################################### # # Index Recovery and Tracking Configuration # # ######################################### # diff --git a/config/alfresco/site-services-context.xml b/config/alfresco/site-services-context.xml index 932b701437..e39cd5e34d 100644 --- a/config/alfresco/site-services-context.xml +++ b/config/alfresco/site-services-context.xml @@ -108,6 +108,7 @@ + diff --git a/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml b/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml index a1478b7411..b8dcd9201c 100644 --- a/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml +++ b/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml @@ -63,6 +63,7 @@ + diff --git a/config/alfresco/subsystems/fileServers/default/file-servers-context.xml b/config/alfresco/subsystems/fileServers/default/file-servers-context.xml index 53cc504555..5ce1ff1b93 100644 --- a/config/alfresco/subsystems/fileServers/default/file-servers-context.xml +++ b/config/alfresco/subsystems/fileServers/default/file-servers-context.xml @@ -49,6 +49,18 @@ + + + + + + + + + + + + @@ -198,11 +210,6 @@ ${ftp.port} - - - ${ftp.ipv6.enabled} - - diff --git a/config/alfresco/subsystems/fileServers/default/file-servers.properties b/config/alfresco/subsystems/fileServers/default/file-servers.properties index 9c2ed117aa..b6dc2e0b6f 100644 --- a/config/alfresco/subsystems/fileServers/default/file-servers.properties +++ b/config/alfresco/subsystems/fileServers/default/file-servers.properties @@ -9,6 +9,11 @@ filesystem.rootPath=${protocols.rootPath} # File name patterns that trigger rename shuffle detection filesystem.renameShufflePattern=(.*\\.tmp)|(.*\\.wbk)|(.*\\.bak)|(.*\\~) +# Should we ever set the read only flag on folders? This may cause problematic +# behaviour in Windows clients. See ALF-6727. +filesystem.setReadOnlyFlagOnFolders=false + + ### CIFS Server Configuration ### cifs.enabled=true cifs.serverName=${localname}A @@ -47,7 +52,6 @@ cifs.sessionDebug= ### FTP Server Configuration ### ftp.enabled=true ftp.port=21 -ftp.ipv6.enabled=false # FTP data port range, a value of 0:0 disables the data port range and will use the next available port # Valid range is 1024-65535 @@ -68,6 +72,8 @@ ftp.sessionDebug= ### NFS Server Configuration ### nfs.enabled=false +# NodeMonitor to update cache of NFS server on nodes renaming and deleting not through NFS protocol +nfs.nodeMinitor.enabled=${nfs.enabled} # Mount/NFS server ports, 0 will allocate next available port nfs.mountServerPort=0 nfs.nfsServerPort=2049 diff --git a/config/alfresco/network-protocol-context.xml b/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml similarity index 90% rename from config/alfresco/network-protocol-context.xml rename to config/alfresco/subsystems/fileServers/default/network-protocol-context.xml index c49a630854..14ba2e70b9 100644 --- a/config/alfresco/network-protocol-context.xml +++ b/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml @@ -67,6 +67,8 @@ + + @@ -84,6 +86,12 @@ ${server.transaction.allow-writes} + ${filesystem.setReadOnlyFlagOnFolders} + + + {http://www.alfresco.org/model/forum/1.0}forum + + diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index e8c8ea905d..57385f4f17 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=4303 +version.schema=4306 diff --git a/config/alfresco/wcm-services-context.xml b/config/alfresco/wcm-services-context.xml index 74b56d21b1..1adc22fbcf 100644 --- a/config/alfresco/wcm-services-context.xml +++ b/config/alfresco/wcm-services-context.xml @@ -45,6 +45,7 @@ + diff --git a/config/alfresco/workflow/invitation-moderated-workflow-messages_ja.properties b/config/alfresco/workflow/invitation-moderated-workflow-messages_ja.properties index 96a13ea0a2..0acdf2b107 100755 --- a/config/alfresco/workflow/invitation-moderated-workflow-messages_ja.properties +++ b/config/alfresco/workflow/invitation-moderated-workflow-messages_ja.properties @@ -5,7 +5,7 @@ # imwf_invitation-moderated.workflow.title=\u62db\u5f85 - \u30e2\u30c7\u30ec\u30fc\u30c8\u6e08 -imwf_invitation-moderated.workflow.description=Web\u30fb\u30b5\u30a4\u30c8\u306a\u3069\u306e\u30ea\u30bd\u30fc\u30b9\u306b\u30e2\u30c7\u30ec\u30fc\u30c8\u3055\u308c\u305f\u62db\u5f85 +imwf_invitation-moderated.workflow.description=Web\u30b5\u30a4\u30c8\u306a\u3069\u306e\u30ea\u30bd\u30fc\u30b9\u306b\u30e2\u30c7\u30ec\u30fc\u30c8\u3055\u308c\u305f\u62db\u5f85 imwf_invitation-moderated-workflow-model.type.imwf_moderatedInvitationReviewTask.title=\u30e2\u30c7\u30ec\u30fc\u30c8\u3055\u308c\u305f\u30b5\u30a4\u30c8\u62db\u5f85 imwf_invitation-moderated-workflow-model.type.imwf_moderatedInvitationReviewTask.description=\u30e2\u30c7\u30ec\u30fc\u30c8\u3055\u308c\u305f\u62db\u5f85\u306e\u958b\u59cb diff --git a/config/alfresco/workflow/wcm-workflow-messages_ja.properties b/config/alfresco/workflow/wcm-workflow-messages_ja.properties index de5f013b35..823be36729 100755 --- a/config/alfresco/workflow/wcm-workflow-messages_ja.properties +++ b/config/alfresco/workflow/wcm-workflow-messages_ja.properties @@ -4,7 +4,7 @@ # Submit Workflow # -wcmwf_submit.workflow.title=Web\u30fb\u30b5\u30a4\u30c8\u63d0\u51fa +wcmwf_submit.workflow.title=Web\u30b5\u30a4\u30c8\u63d0\u51fa wcmwf_submit.workflow.description=\u627f\u8a8d\u7528\u306b\u5909\u66f4\u3092\u9001\u4fe1 wcmwf_submit.node.verifybrokenlinks.transition.abort.title=\u63d0\u51fa\u306e\u505c\u6b62 wcmwf_submit.node.verifybrokenlinks.transition.abort.description=\u63d0\u51fa\u306e\u505c\u6b62 @@ -27,7 +27,7 @@ wcmwf_submit.node.submitpending.transition.cancel.description=\u63d0\u51fa\u306e wcmwf_submit.node.submitpending.transition.launch.title=\u4eca\u3059\u3050\u9001\u4fe1 wcmwf_submit.node.submitpending.transition.launch.description=\u4eca\u3059\u3050\u9001\u4fe1 -wcmwf_submitdirect.workflow.title=Web\u30fb\u30b5\u30a4\u30c8\u63d0\u51fa\uff08\u76f4\u63a5\uff09 +wcmwf_submitdirect.workflow.title=Web\u30b5\u30a4\u30c8\u63d0\u51fa\uff08\u76f4\u63a5\uff09 wcmwf_submitdirect.workflow.description=\u30b9\u30c6\u30fc\u30b8\u30f3\u30b0\u30fb\u30b5\u30f3\u30c9\u30dc\u30c3\u30af\u30b9\u306b\u5909\u66f4\u3092\u76f4\u63a5\u9001\u4fe1 diff --git a/config/test/alfresco/model/testWcmModel.xml b/config/test/alfresco/model/testWcmModel.xml new file mode 100644 index 0000000000..61f5238c96 --- /dev/null +++ b/config/test/alfresco/model/testWcmModel.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + d:date + true + + + + + + diff --git a/config/test/alfresco/wcm-template-node-test-context.xml b/config/test/alfresco/wcm-template-node-test-context.xml new file mode 100644 index 0000000000..d115215c07 --- /dev/null +++ b/config/test/alfresco/wcm-template-node-test-context.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + test/alfresco/model/testWcmModel.xml + + + + + diff --git a/source/java/org/alfresco/cmis/CMISRelationshipDirectionEnum.java b/source/java/org/alfresco/cmis/CMISRelationshipDirectionEnum.java index 5ca5151d9c..39b8f432a3 100644 --- a/source/java/org/alfresco/cmis/CMISRelationshipDirectionEnum.java +++ b/source/java/org/alfresco/cmis/CMISRelationshipDirectionEnum.java @@ -27,7 +27,7 @@ public enum CMISRelationshipDirectionEnum implements EnumLabel { SOURCE("source"), TARGET("target"), - BOTH("both"); + EITHER("either"); private String label; diff --git a/source/java/org/alfresco/cmis/CMISServices.java b/source/java/org/alfresco/cmis/CMISServices.java index 30d8cf3323..91a16a65f5 100644 --- a/source/java/org/alfresco/cmis/CMISServices.java +++ b/source/java/org/alfresco/cmis/CMISServices.java @@ -673,7 +673,7 @@ public interface CMISServices */ public void deleteObject(String objectId, boolean allVersions) throws CMISConstraintException, CMISVersioningException, CMISObjectNotFoundException, CMISInvalidArgumentException, - CMISPermissionDeniedException, CMISRuntimeException; + CMISPermissionDeniedException, CMISRuntimeException, CMISServiceException; /** * Adds a secondary child association to an object from a folder. diff --git a/source/java/org/alfresco/cmis/dictionary/CMISBasePropertyDefinition.java b/source/java/org/alfresco/cmis/dictionary/CMISBasePropertyDefinition.java index dc080973a4..60163cb066 100644 --- a/source/java/org/alfresco/cmis/dictionary/CMISBasePropertyDefinition.java +++ b/source/java/org/alfresco/cmis/dictionary/CMISBasePropertyDefinition.java @@ -50,6 +50,7 @@ import org.alfresco.service.cmr.dictionary.Constraint; import org.alfresco.service.cmr.dictionary.ConstraintDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.util.ISO9075; /** * CMIS Property Definition @@ -109,7 +110,7 @@ public class CMISBasePropertyDefinition implements CMISPropertyDefinition, Seria { this.propertyId = propertyId; this.typeDef = typeDef; - queryName = cmisMapping.buildPrefixEncodedString(propertyId.getQName()); + queryName = ISO9075.encodeSQL(cmisMapping.buildPrefixEncodedString(propertyId.getQName())); displayName = (propDef.getTitle() != null) ? propDef.getTitle() : propertyId.getId(); description = propDef.getDescription() != null ? propDef.getDescription() : displayName; propertyType = cmisMapping.getDataType(propDef.getDataType()); diff --git a/source/java/org/alfresco/cmis/dictionary/CMISDocumentTypeDefinition.java b/source/java/org/alfresco/cmis/dictionary/CMISDocumentTypeDefinition.java index bce1e97282..2d250a7659 100644 --- a/source/java/org/alfresco/cmis/dictionary/CMISDocumentTypeDefinition.java +++ b/source/java/org/alfresco/cmis/dictionary/CMISDocumentTypeDefinition.java @@ -25,6 +25,7 @@ import org.alfresco.cmis.CMISTypeId; import org.alfresco.cmis.mapping.CMISMapping; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.namespace.QName; +import org.alfresco.util.ISO9075; /** @@ -69,7 +70,7 @@ public class CMISDocumentTypeDefinition extends CMISAbstractTypeDefinition } else { - objectTypeQueryName = cmisMapping.buildPrefixEncodedString(typeId.getQName()); + objectTypeQueryName = ISO9075.encodeSQL(cmisMapping.buildPrefixEncodedString(typeId.getQName())); if (cmisMapping.isValidCmisDocument(parentQName)) { parentTypeId = cmisMapping.getCmisTypeId(CMISScope.DOCUMENT, parentQName); diff --git a/source/java/org/alfresco/cmis/dictionary/CMISFolderTypeDefinition.java b/source/java/org/alfresco/cmis/dictionary/CMISFolderTypeDefinition.java index 57f3964de1..0713768454 100644 --- a/source/java/org/alfresco/cmis/dictionary/CMISFolderTypeDefinition.java +++ b/source/java/org/alfresco/cmis/dictionary/CMISFolderTypeDefinition.java @@ -25,6 +25,7 @@ import org.alfresco.cmis.mapping.CMISMapping; import org.alfresco.model.ContentModel; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.namespace.QName; +import org.alfresco.util.ISO9075; /** @@ -63,7 +64,7 @@ public class CMISFolderTypeDefinition extends CMISAbstractTypeDefinition } else { - objectTypeQueryName = cmisMapping.buildPrefixEncodedString(typeId.getQName()); + objectTypeQueryName = ISO9075.encodeSQL(cmisMapping.buildPrefixEncodedString(typeId.getQName())); if (cmisMapping.isValidCmisFolder(parentQName)) { parentTypeId = cmisMapping.getCmisTypeId(CMISScope.FOLDER, parentQName); diff --git a/source/java/org/alfresco/cmis/dictionary/CMISObjectTypeDefinition.java b/source/java/org/alfresco/cmis/dictionary/CMISObjectTypeDefinition.java index 0fe32d040d..6486479944 100644 --- a/source/java/org/alfresco/cmis/dictionary/CMISObjectTypeDefinition.java +++ b/source/java/org/alfresco/cmis/dictionary/CMISObjectTypeDefinition.java @@ -27,6 +27,7 @@ import org.alfresco.cmis.mapping.CMISMapping; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.ISO9075; /** @@ -52,7 +53,7 @@ public class CMISObjectTypeDefinition extends CMISAbstractTypeDefinition // Object type properties objectTypeId = typeId; - objectTypeQueryName = cmisMapping.buildPrefixEncodedString(typeId.getQName()); + objectTypeQueryName = ISO9075.encodeSQL(cmisMapping.buildPrefixEncodedString(typeId.getQName())); if (cmisClassDef != null) { diff --git a/source/java/org/alfresco/cmis/dictionary/CMISPolicyTypeDefinition.java b/source/java/org/alfresco/cmis/dictionary/CMISPolicyTypeDefinition.java index 12dd170896..1114227abf 100644 --- a/source/java/org/alfresco/cmis/dictionary/CMISPolicyTypeDefinition.java +++ b/source/java/org/alfresco/cmis/dictionary/CMISPolicyTypeDefinition.java @@ -31,6 +31,7 @@ import org.alfresco.cmis.mapping.CMISMapping; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.ISO9075; /** @@ -68,7 +69,7 @@ public class CMISPolicyTypeDefinition extends CMISAbstractTypeDefinition } else { - objectTypeQueryName = cmisMapping.buildPrefixEncodedString(typeId.getQName()); + objectTypeQueryName = ISO9075.encodeSQL(cmisMapping.buildPrefixEncodedString(typeId.getQName())); parentTypeId = CMISDictionaryModel.POLICY_TYPE_ID; } description = cmisClassDef.getDescription() != null ? cmisClassDef.getDescription() : displayName; diff --git a/source/java/org/alfresco/cmis/dictionary/CMISRelationshipTypeDefinition.java b/source/java/org/alfresco/cmis/dictionary/CMISRelationshipTypeDefinition.java index c38eb13883..57f1deafef 100644 --- a/source/java/org/alfresco/cmis/dictionary/CMISRelationshipTypeDefinition.java +++ b/source/java/org/alfresco/cmis/dictionary/CMISRelationshipTypeDefinition.java @@ -36,6 +36,7 @@ import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.ISO9075; /** @@ -95,7 +96,7 @@ public class CMISRelationshipTypeDefinition extends CMISAbstractTypeDefinition { creatable = true; displayName = (assocDef.getTitle() != null) ? assocDef.getTitle() : typeId.getId(); - objectTypeQueryName = cmisMapping.buildPrefixEncodedString(typeId.getQName()); + objectTypeQueryName = ISO9075.encodeSQL(cmisMapping.buildPrefixEncodedString(typeId.getQName())); parentTypeId = CMISDictionaryModel.RELATIONSHIP_TYPE_ID; description = assocDef.getDescription() != null ? assocDef.getDescription() : displayName; diff --git a/source/java/org/alfresco/cmis/dictionary/CMISStrictDictionaryService.java b/source/java/org/alfresco/cmis/dictionary/CMISStrictDictionaryService.java index 96aca1a748..05ee07c46d 100644 --- a/source/java/org/alfresco/cmis/dictionary/CMISStrictDictionaryService.java +++ b/source/java/org/alfresco/cmis/dictionary/CMISStrictDictionaryService.java @@ -22,6 +22,7 @@ import java.util.Collection; import org.alfresco.cmis.CMISScope; import org.alfresco.cmis.CMISTypeId; +import org.alfresco.cmis.mapping.CMISMapping; import org.alfresco.model.ContentModel; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; @@ -67,6 +68,8 @@ public class CMISStrictDictionaryService extends CMISAbstractDictionaryService CMISTypeId typeId = cmisMapping.getCmisTypeId(classQName); if (typeId == null) continue; + if (typeId.getScope() == CMISScope.RELATIONSHIP) + continue; // create appropriate kind of type definition ClassDefinition classDef = dictionaryService.getClass(cmisMapping.getCmisType(typeId.getQName())); @@ -80,11 +83,6 @@ public class CMISStrictDictionaryService extends CMISAbstractDictionaryService boolean isSystem = dictionaryService.isSubClass(classDef.getName(), ContentModel.TYPE_SYSTEM_FOLDER); objectTypeDef = new CMISFolderTypeDefinition(cmisMapping, typeId, classDef, isSystem); } - else if (typeId.getScope() == CMISScope.RELATIONSHIP) - { - AssociationDefinition assocDef = dictionaryService.getAssociation(classQName); - objectTypeDef = new CMISRelationshipTypeDefinition(cmisMapping, typeId, classDef, assocDef); - } else if (typeId.getScope() == CMISScope.POLICY) { objectTypeDef = new CMISPolicyTypeDefinition(cmisMapping, typeId, classDef); @@ -106,15 +104,20 @@ public class CMISStrictDictionaryService extends CMISAbstractDictionaryService */ private void createAssocDefs(DictionaryRegistry registry, Collection classQNames) { + CMISTypeId typeId = cmisMapping.getCmisTypeId(CMISScope.RELATIONSHIP, CMISMapping.RELATIONSHIP_QNAME); + ClassDefinition classDef = dictionaryService.getClass(cmisMapping.getCmisType(typeId.getQName())); + CMISAbstractTypeDefinition objectTypeDef = new CMISRelationshipTypeDefinition(cmisMapping, typeId, classDef, null); + registry.registerTypeDefinition(objectTypeDef); + for (QName classQName : classQNames) { if (!cmisMapping.isValidCmisRelationship(classQName)) continue; // create appropriate kind of type definition - CMISTypeId typeId = cmisMapping.getCmisTypeId(CMISScope.RELATIONSHIP, classQName); + typeId = cmisMapping.getCmisTypeId(CMISScope.RELATIONSHIP, classQName); AssociationDefinition assocDef = dictionaryService.getAssociation(classQName); - CMISAbstractTypeDefinition objectTypeDef = new CMISRelationshipTypeDefinition(cmisMapping, typeId, null, assocDef); + objectTypeDef = new CMISRelationshipTypeDefinition(cmisMapping, typeId, null, assocDef); registry.registerTypeDefinition(objectTypeDef); } diff --git a/source/java/org/alfresco/cmis/mapping/CMISMapping.java b/source/java/org/alfresco/cmis/mapping/CMISMapping.java index 13c129950d..8891a654e8 100644 --- a/source/java/org/alfresco/cmis/mapping/CMISMapping.java +++ b/source/java/org/alfresco/cmis/mapping/CMISMapping.java @@ -23,7 +23,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -238,7 +237,7 @@ public class CMISMapping implements InitializingBean registerEvaluator(CMISScope.DOCUMENT, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_GET_ACL, PermissionService.READ_PERMISSIONS)); registerEvaluator(CMISScope.DOCUMENT, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_APPLY_ACL, PermissionService.CHANGE_PERMISSIONS)); - registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_DELETE_OBJECT, PermissionService.DELETE_NODE)); + registerEvaluator(CMISScope.FOLDER, new RootActionEvaluator(new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_DELETE_OBJECT, PermissionService.DELETE_NODE), false)); registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_UPDATE_PROPERTIES, PermissionService.WRITE_PROPERTIES)); registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_GET_FOLDER_TREE, PermissionService.READ_CHILDREN)); registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_GET_PROPERTIES, PermissionService.READ_PROPERTIES)); @@ -247,7 +246,7 @@ public class CMISMapping implements InitializingBean registerEvaluator(CMISScope.FOLDER, new ParentActionEvaluator(new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_GET_FOLDER_PARENT, PermissionService.READ_PERMISSIONS))); registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_GET_DESCENDANTS, PermissionService.READ_CHILDREN)); // Is CAN_MOVE_OBJECT correct mapping? - registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_MOVE_OBJECT, PermissionService.DELETE_NODE)); + registerEvaluator(CMISScope.FOLDER, new RootActionEvaluator(new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_MOVE_OBJECT, PermissionService.DELETE_NODE), false)); registerEvaluator(CMISScope.FOLDER, new FixedValueActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_APPLY_POLICY, false)); registerEvaluator(CMISScope.FOLDER, new FixedValueActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_GET_APPLIED_POLICIES, true)); registerEvaluator(CMISScope.FOLDER, new FixedValueActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_REMOVE_POLICY, false)); @@ -255,7 +254,7 @@ public class CMISMapping implements InitializingBean registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_CREATE_DOCUMENT, PermissionService.CREATE_CHILDREN)); registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_CREATE_FOLDER, PermissionService.CREATE_CHILDREN)); registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_CREATE_RELATIONSHIP, PermissionService.CREATE_ASSOCIATIONS)); - registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_DELETE_TREE, PermissionService.DELETE_NODE)); + registerEvaluator(CMISScope.FOLDER, new RootActionEvaluator(new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_DELETE_TREE, PermissionService.DELETE_NODE), false)); registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_GET_ACL, PermissionService.READ_PERMISSIONS)); registerEvaluator(CMISScope.FOLDER, new PermissionActionEvaluator(serviceRegistry, CMISAllowedActionEnum.CAN_APPLY_ACL, PermissionService.CHANGE_PERMISSIONS)); @@ -406,14 +405,14 @@ public class CMISMapping implements InitializingBean { return getCmisTypeId(CMISScope.FOLDER, classQName); } - if (isValidCmisRelationship(classQName)) - { - return getCmisTypeId(CMISScope.RELATIONSHIP, classQName); - } if (isValidCmisPolicy(classQName)) { return getCmisTypeId(CMISScope.POLICY, classQName); } + if (isValidCmisRelationship(classQName)) + { + return getCmisTypeId(CMISScope.RELATIONSHIP, classQName); + } return null; } @@ -560,14 +559,6 @@ public class CMISMapping implements InitializingBean { return false; } - if (!isValidCmisDocumentOrFolder(getCmisType(associationDefinition.getSourceClass().getName()))) - { - return false; - } - if (!isValidCmisDocumentOrFolder(getCmisType(associationDefinition.getTargetClass().getName()))) - { - return false; - } return true; } diff --git a/source/java/org/alfresco/cmis/mapping/CMISServicesImpl.java b/source/java/org/alfresco/cmis/mapping/CMISServicesImpl.java index d319b3e880..5025a4e6a0 100644 --- a/source/java/org/alfresco/cmis/mapping/CMISServicesImpl.java +++ b/source/java/org/alfresco/cmis/mapping/CMISServicesImpl.java @@ -692,11 +692,11 @@ public class CMISServicesImpl implements CMISServices, ApplicationContextAware, // retrieve associations List assocs = new ArrayList(); - if (direction == CMISRelationshipDirectionEnum.SOURCE || direction == CMISRelationshipDirectionEnum.BOTH) + if (direction == CMISRelationshipDirectionEnum.SOURCE || direction == CMISRelationshipDirectionEnum.EITHER) { assocs.addAll(nodeService.getTargetAssocs(node, RegexQNamePattern.MATCH_ALL)); } - if (direction == CMISRelationshipDirectionEnum.TARGET || direction == CMISRelationshipDirectionEnum.BOTH) + if (direction == CMISRelationshipDirectionEnum.TARGET || direction == CMISRelationshipDirectionEnum.EITHER) { assocs.addAll(nodeService.getSourceAssocs(node, RegexQNamePattern.MATCH_ALL)); } @@ -709,12 +709,16 @@ public class CMISServicesImpl implements CMISServices, ApplicationContextAware, for (AssociationRef assoc : assocs) { CMISTypeDefinition assocTypeDef = cmisDictionaryService.findTypeForClass(assoc.getTypeQName(), CMISScope.RELATIONSHIP); - if (assocTypeDef != null) + QName sourceTypeDef = nodeService.getType(assoc.getSourceRef()); + QName targetTypeDef = nodeService.getType(assoc.getTargetRef()); + if (assocTypeDef == null || cmisDictionaryService.findTypeForClass(sourceTypeDef) == null || + cmisDictionaryService.findTypeForClass(targetTypeDef) == null) { - if (assocTypeDef.equals(relDef) || (subRelDefs != null && subRelDefs.contains(assocTypeDef))) - { - filteredAssocs.add(assoc); - } + continue; + } + if (assocTypeDef.equals(relDef) || (subRelDefs != null && subRelDefs.contains(assocTypeDef))) + { + filteredAssocs.add(assoc); } } @@ -1466,7 +1470,7 @@ public class CMISServicesImpl implements CMISServices, ApplicationContextAware, */ public void deleteObject(String objectId, boolean allVersions) throws CMISConstraintException, CMISVersioningException, CMISObjectNotFoundException, CMISInvalidArgumentException, - CMISPermissionDeniedException, CMISRuntimeException + CMISPermissionDeniedException, CMISRuntimeException, CMISServiceException { try { @@ -1533,6 +1537,10 @@ public class CMISServicesImpl implements CMISServices, ApplicationContextAware, // Attempt to delete the node nodeService.deleteNode(nodeRef); } + catch (CMISServiceException e) + { + throw e; + } catch (AccessDeniedException e) { throw new CMISPermissionDeniedException(e); diff --git a/source/java/org/alfresco/cmis/mapping/RootActionEvaluator.java b/source/java/org/alfresco/cmis/mapping/RootActionEvaluator.java new file mode 100644 index 0000000000..37791f8897 --- /dev/null +++ b/source/java/org/alfresco/cmis/mapping/RootActionEvaluator.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.cmis.mapping; + +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Action Evaluator which evaluates on whether node is root or not + * + * @author davidc + */ +public class RootActionEvaluator extends AbstractActionEvaluator +{ + private AbstractActionEvaluator evaluator; + private boolean allow; + + /** + * Construct + * + * @param serviceRegistry + * @param action + */ + protected RootActionEvaluator(AbstractActionEvaluator evaluator, boolean allow) + { + super(evaluator.getServiceRegistry(), evaluator.getAction()); + this.evaluator = evaluator; + this.allow = allow; + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.CMISActionEvaluator#isAllowed(org.alfresco.service.cmr.repository.NodeRef) + */ + public boolean isAllowed(NodeRef nodeRef) + { + if (nodeRef.equals(getServiceRegistry().getCMISService().getDefaultRootNodeRef())) + { + return allow; + } + return evaluator.isAllowed(nodeRef); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + builder.append("RootActionEvaluator[evaluator=").append(evaluator).append(",allow=").append(allow).append("]"); + return builder.toString(); + } +} + diff --git a/source/java/org/alfresco/cmis/search/CMIS-query-test-model.xml b/source/java/org/alfresco/cmis/search/CMIS-query-test-model.xml index 2ca70e9f2e..83a7853ac5 100644 --- a/source/java/org/alfresco/cmis/search/CMIS-query-test-model.xml +++ b/source/java/org/alfresco/cmis/search/CMIS-query-test-model.xml @@ -292,6 +292,30 @@ Extended Folder cm:folder + + + Type that requires encoding + cm:content + true + + + d:text + false + false + + true + both + + + + + + + Type that requires encoding + true + + + \ No newline at end of file diff --git a/source/java/org/alfresco/cmis/search/CmisFunctionEvaluationContext.java b/source/java/org/alfresco/cmis/search/CmisFunctionEvaluationContext.java index c762445198..57c5faecec 100644 --- a/source/java/org/alfresco/cmis/search/CmisFunctionEvaluationContext.java +++ b/source/java/org/alfresco/cmis/search/CmisFunctionEvaluationContext.java @@ -20,6 +20,7 @@ package org.alfresco.cmis.search; import java.io.Serializable; import java.util.Collection; +import java.util.HashSet; import java.util.Map; import org.alfresco.cmis.CMISCardinalityEnum; @@ -38,6 +39,7 @@ import org.alfresco.repo.search.impl.querymodel.QueryModelException; import org.alfresco.repo.search.impl.querymodel.Selector; import org.alfresco.repo.search.impl.querymodel.impl.functions.Lower; import org.alfresco.repo.search.impl.querymodel.impl.functions.Upper; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.apache.lucene.queryParser.ParseException; @@ -48,6 +50,8 @@ import org.apache.lucene.search.Query; */ public class CmisFunctionEvaluationContext implements FunctionEvaluationContext { + private static HashSet EXPOSED_FIELDS = new HashSet(); + public static CMISScope[] STRICT_SCOPES = new CMISScope[] { CMISScope.DOCUMENT, CMISScope.FOLDER }; public static CMISScope[] ALFRESCO_SCOPES = new CMISScope[] { CMISScope.DOCUMENT, CMISScope.FOLDER, CMISScope.POLICY }; @@ -64,6 +68,54 @@ public class CmisFunctionEvaluationContext implements FunctionEvaluationContext private Float score; + static + { + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_PATH); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_TEXT); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_ID); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_ISROOT); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_ISNODE); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_TX); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_PARENT); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_PRIMARYPARENT); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_QNAME); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_CLASS); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_TYPE); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_EXACTTYPE); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_ASPECT); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_EXACTASPECT); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_ALL); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_ISUNSET); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_ISNULL); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_ISNOTNULL); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_FTSSTATUS); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_ASSOCTYPEQNAME); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_PRIMARYASSOCTYPEQNAME); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_DBID); + EXPOSED_FIELDS.add(LuceneQueryParser.FIELD_TAG); + + + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.ANY.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.ASSOC_REF.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.BOOLEAN.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.CATEGORY.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.CHILD_ASSOC_REF.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.CONTENT.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.DATE.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.DATETIME.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.DOUBLE.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.FLOAT.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.INT.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.LOCALE.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.LONG.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.MLTEXT.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.NODE_REF.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.PATH.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.PERIOD.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.QNAME.getLocalName()); + EXPOSED_FIELDS.add("d:"+DataTypeDefinition.TEXT.getLocalName()); + } + /** * @param nodeRefs * the nodeRefs to set @@ -363,7 +415,14 @@ public class CmisFunctionEvaluationContext implements FunctionEvaluationContext CMISPropertyDefinition propDef = cmisDictionaryService.findPropertyByQueryName(propertyName); if (propDef == null) { - throw new CMISQueryException("Unknown column/property " + propertyName); + if (EXPOSED_FIELDS.contains(propertyName)) + { + return; + } + else + { + throw new CMISQueryException("Unknown column/property " + propertyName); + } } CMISTypeDefinition typeDef = cmisDictionaryService.findTypeForClass(selector.getType(), validScopes); diff --git a/source/java/org/alfresco/cmis/search/QueryTest.java b/source/java/org/alfresco/cmis/search/QueryTest.java index 7373384591..e38f7645e4 100644 --- a/source/java/org/alfresco/cmis/search/QueryTest.java +++ b/source/java/org/alfresco/cmis/search/QueryTest.java @@ -96,6 +96,7 @@ import org.antlr.runtime.CommonTokenStream; import org.antlr.runtime.RecognitionException; import org.antlr.runtime.tree.CommonTree; import org.alfresco.util.CachingDateFormat; +import org.alfresco.util.ISO9075; import org.springframework.extensions.surf.util.I18NUtil; /** @@ -105,6 +106,14 @@ public class QueryTest extends BaseCMISTest { private static final String TEST_NAMESPACE = "http://www.alfresco.org/test/cmis-query-test"; + + + QName typeThatRequiresEncoding = QName.createQName(TEST_NAMESPACE, "type-that-requires-encoding"); + + QName aspectThatRequiresEncoding = QName.createQName(TEST_NAMESPACE, "aspect-that-requires-encoding"); + + QName propertyThatRequiresEncoding = QName.createQName(TEST_NAMESPACE, "property-that-requires-encoding"); + QName extendedContent = QName.createQName(TEST_NAMESPACE, "extendedContent"); QName singleTextBoth = QName.createQName(TEST_NAMESPACE, "singleTextBoth"); @@ -583,6 +592,15 @@ public class QueryTest extends BaseCMISTest } + public void testEncodingOfTypeAndPropertyNames() + { + addTypeTestDataModel(); + assertNotNull("Type not found by query name "+ISO9075.encodeSQL(typeThatRequiresEncoding.toPrefixString(namespaceService)), cmisDictionaryService.findTypeByQueryName(ISO9075.encodeSQL(typeThatRequiresEncoding.toPrefixString(namespaceService)))); + assertNotNull("Aspect not found by query name "+ISO9075.encodeSQL(aspectThatRequiresEncoding.toPrefixString(namespaceService)), cmisDictionaryService.findTypeByQueryName(ISO9075.encodeSQL(aspectThatRequiresEncoding.toPrefixString(namespaceService)))); + assertNotNull("Prpo not found by query name "+ISO9075.encodeSQL(propertyThatRequiresEncoding.toPrefixString(namespaceService)), cmisDictionaryService.findPropertyByQueryName(ISO9075.encodeSQL(propertyThatRequiresEncoding.toPrefixString(namespaceService)))); + + } + public void test_ALLOWED_CHILD_OBJECT_TYPES() throws Exception { CMISQueryOptions options = new CMISQueryOptions("SELECT * FROM cmis:Folder", rootNodeRef.getStoreRef()); @@ -3290,6 +3308,9 @@ public class QueryTest extends BaseCMISTest testQuery("SELECT * FROM cmis:document WHERE CONTAINS('\\'quick\\'')", 1, false, "cmis:objectId", new String(), false); testExtendedQuery("SELECT * FROM cmis:document D WHERE CONTAINS(D, 'cmis:name:\\'Tutorial\\'')", 1, false, "cmis:objectId", new String(), false); testExtendedQuery("SELECT cmis:name as BOO FROM cmis:document D WHERE CONTAINS('BOO:\\'Tutorial\\'')", 1, false, "cmis:objectId", new String(), false); + testExtendedQuery("SELECT * FROM cmis:document D WHERE CONTAINS('TEXT:\\'zebra\\'')", doc_count-1, false, "cmis:objectId", new String(), false); + testExtendedQuery("SELECT * FROM cmis:document D WHERE CONTAINS('ALL:\\'zebra\\'')", doc_count-1, false, "cmis:objectId", new String(), false); + testExtendedQuery("SELECT * FROM cmis:document D WHERE CONTAINS('d:content:\\'zebra\\'')", doc_count-1, false, "cmis:objectId", new String(), false); } public void testScoreValues() @@ -3823,6 +3844,23 @@ public class QueryTest extends BaseCMISTest testQuery("SELECT * FROM test:extendedContent WHERE 'Un tokenised' = ANY test:singleTextBoth ", 1, false, "cmis:name", new String(), true); testQuery("SELECT * FROM test:extendedContent WHERE ANY test:singleTextBoth IN ('Un tokenised', 'Monkey')", 1, false, "cmis:name", new String(), true); testQuery("SELECT * FROM test:extendedContent WHERE ANY test:singleTextBoth NOT IN ('Un tokenized')", 1, false, "cmis:name", new String(), true); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth < 'tokenised'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth < 'Un tokenised'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth < 'V'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth < 'U'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth <= 'tokenised'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth <= 'Un tokenised'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth <= 'V'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth <= 'U'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth > 'tokenised'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth > 'Un tokenised'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth > 'V'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth > 'U'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth >= 'tokenised'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth >= 'Un tokenised'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth >= 'V'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextBoth >= 'U'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised = 'Un tokenised'", 1, false, "cmis:name", new String(), false); testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised <> 'tokenised'", 1, false, "cmis:name", new String(), false); @@ -3833,6 +3871,22 @@ public class QueryTest extends BaseCMISTest testQuery("SELECT * FROM test:extendedContent WHERE 'Un tokenised' = ANY test:singleTextUntokenised ", 1, false, "cmis:name", new String(), true); testQuery("SELECT * FROM test:extendedContent WHERE ANY test:singleTextUntokenised IN ('Un tokenised', 'Monkey')", 1, false, "cmis:name", new String(), true); testQuery("SELECT * FROM test:extendedContent WHERE ANY test:singleTextUntokenised NOT IN ('Un tokenized')", 1, false, "cmis:name", new String(), true); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised < 'tokenised'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised < 'Un tokenised'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised < 'V'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised < 'U'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised <= 'tokenised'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised <= 'Un tokenised'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised <= 'V'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised <= 'U'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised > 'tokenised'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised > 'Un tokenised'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised > 'V'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised > 'U'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised >= 'tokenised'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised >= 'Un tokenised'", 1, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised >= 'V'", 0, false, "cmis:name", new String(), false); + testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextUntokenised >= 'U'", 1, false, "cmis:name", new String(), false); testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextTokenised = 'tokenised'", 1, false, "cmis:name", new String(), false); testQuery("SELECT * FROM test:extendedContent WHERE test:singleTextTokenised <> 'tokenized'", 1, false, "cmis:name", new String(), false); @@ -3843,6 +3897,7 @@ public class QueryTest extends BaseCMISTest testQuery("SELECT * FROM test:extendedContent WHERE 'tokenised' = ANY test:singleTextTokenised ", 1, false, "cmis:name", new String(), true); testQuery("SELECT * FROM test:extendedContent WHERE ANY test:singleTextTokenised IN ('tokenised', 'Monkey')", 1, false, "cmis:name", new String(), true); testQuery("SELECT * FROM test:extendedContent WHERE ANY test:singleTextTokenised NOT IN ('tokenized')", 1, false, "cmis:name", new String(), true); + // Ranges do not make a lot of sense for tokenized fields // d:text single by alias diff --git a/source/java/org/alfresco/filesys/NFSServerBean.java b/source/java/org/alfresco/filesys/NFSServerBean.java index c5d7a24287..b169f2ff84 100644 --- a/source/java/org/alfresco/filesys/NFSServerBean.java +++ b/source/java/org/alfresco/filesys/NFSServerBean.java @@ -55,6 +55,8 @@ public class NFSServerBean extends AbstractLifecycleBean private ServerConfiguration m_filesysConfig; private NFSConfigSection m_nfsConfig; + private NfsServerNodeMonitor nodeMonitor; + // List of NFS server components private Vector m_serverList = new Vector(); @@ -79,6 +81,11 @@ public class NFSServerBean extends AbstractLifecycleBean return m_filesysConfig; } + public void setNodeMonitor(NfsServerNodeMonitor nodeMonitor) + { + this.nodeMonitor = nodeMonitor; + } + /** * Check if the server is started/enabled * @@ -113,7 +120,12 @@ public class NFSServerBean extends AbstractLifecycleBean // Create the mount and main NFS servers m_serverList.add(new MountServer(m_filesysConfig)); - m_serverList.add(new NFSServer(m_filesysConfig)); + NFSServer nfsServer = new NFSServer(m_filesysConfig); + m_serverList.add(nfsServer); + if (null != nodeMonitor) + { + nodeMonitor.setNfsServer(nfsServer); + } // Add the servers to the configuration @@ -152,6 +164,11 @@ public class NFSServerBean extends AbstractLifecycleBean */ public final void stopServer() { + if (null != nodeMonitor) + { + nodeMonitor.setEnabled(false); + } + if (m_filesysConfig == null) { // initialisation failed diff --git a/source/java/org/alfresco/filesys/NfsServerNodeMonitor.java b/source/java/org/alfresco/filesys/NfsServerNodeMonitor.java new file mode 100644 index 0000000000..52013f9b60 --- /dev/null +++ b/source/java/org/alfresco/filesys/NfsServerNodeMonitor.java @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2006-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.filesys; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.filesys.repo.ContentContext; +import org.alfresco.jlan.oncrpc.nfs.NFSServer; +import org.alfresco.jlan.oncrpc.nfs.ShareDetails; +import org.alfresco.jlan.server.core.DeviceContext; +import org.alfresco.jlan.server.filesys.FileName; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.node.NodeServicePolicies; +import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy; +import org.alfresco.repo.node.NodeServicePolicies.OnDeleteNodePolicy; +import org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.QName; +import org.apache.log4j.Logger; +import org.springframework.beans.factory.InitializingBean; + +/** + * Node monitor for NFS server which updates NFS cache on renaming or deleting nodes not through NFS protocol. This monitor may be dynamically enabled or disabled. It handles nodes + * for ${filesystem.name} device name + * + * @author Dmitry Velichkevich + */ +public class NfsServerNodeMonitor + implements + NodeServicePolicies.OnUpdatePropertiesPolicy, + NodeServicePolicies.BeforeDeleteNodePolicy, + NodeServicePolicies.OnDeleteNodePolicy, + InitializingBean +{ + private static final Logger LOGGER = Logger.getLogger(NfsServerNodeMonitor.class); + + public static final char NIX_SEPARATOR = '/'; + public static final String NIX_SEPARATOR_STR = "/"; + + // + // Not static fields (or bean properties) + // + + private Boolean enabled; + + private String targetDeviceName; + + // Calculable value + private StoreRef targetStoreRef; + + private List filesystemContexts; + + private NodeService nodeService; + private PolicyComponent policyComponent; + private PermissionService permissionService; + + /** + * Calculable value (see {@link NFSServerBean}) + */ + private NFSServer nfsServer; + + private Map cachedNodes = new HashMap(); + + public NfsServerNodeMonitor() + { + } + + /** + * Enables or disables policy handlers + * + * @param enabled {@link Boolean} value which determines working state of the handler + */ + public void setEnabled(boolean enabled) + { + Object previousState = this.enabled; + this.enabled = enabled; + if (null != previousState) + { + initialize(); + } + } + + public Boolean isEnabled() + { + return enabled; + } + + public void setTargetDeviceName(String targetDeviceName) + { + this.targetDeviceName = targetDeviceName; + } + + public String getTargetDeviceName() + { + return targetDeviceName; + } + + public void setFilesystemContexts(List filesystemContexts) + { + this.filesystemContexts = filesystemContexts; + } + + public StoreRef getTargetStoreRef() + { + return targetStoreRef; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + public void setNfsServer(NFSServer nfsServer) + { + this.nfsServer = nfsServer; + } + + @Override + public void afterPropertiesSet() throws Exception + { + initialize(); + } + + /** + * Performs all check on mandatory properties, searches for {@link StoreRef} for root path of target device and registers policy handlers for NFS cache updating on node + * properties updating and node deleting + */ + private void initialize() + { + if (enabled) + { + if (null == filesystemContexts) + { + throw new AlfrescoRuntimeException("'filesystemContexts' property is not configured"); + } + for (DeviceContext context : filesystemContexts) + { + if ((context instanceof ContentContext) && (null != context.getDeviceName()) && context.getDeviceName().equals(targetDeviceName)) + { + ContentContext targetContext = (ContentContext) context; + if (null != targetContext.getStoreName()) + { + targetStoreRef = new StoreRef(targetContext.getStoreName()); + } + break; + } + } + if (null == targetStoreRef) + { + throw new AlfrescoRuntimeException("Target Store Reference can't be found for '" + targetDeviceName + + "' device name. Check correctness of 'targetDeviceName' and 'filesystemContexts' properties configurations"); + } + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("StoreRef='" + targetStoreRef + "' was found for '" + targetDeviceName + "' device name"); + } + + policyComponent.bindClassBehaviour(OnDeleteNodePolicy.QNAME, this, new JavaBehaviour(this, "onDeleteNode")); + policyComponent.bindClassBehaviour(BeforeDeleteNodePolicy.QNAME, this, new JavaBehaviour(this, "beforeDeleteNode")); + policyComponent.bindClassBehaviour(OnUpdatePropertiesPolicy.QNAME, this, new JavaBehaviour(this, "onUpdateProperties")); + } + else + { + LOGGER.warn("NodeMonitor for NFS server is not enabled! Cache of NFS server will be never synchronized with target filesystem"); + } + } + + @Override + public void onUpdateProperties(NodeRef nodeRef, Map before, Map after) + { + if (enabled && (null != nfsServer) && targetStoreRef.equals(nodeRef.getStoreRef())) + { + int dbId = DefaultTypeConverter.INSTANCE.intValue(nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID)); + if (null == findShareDetailsForId(dbId)) + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Node with nodeRef='" + nodeRef + "' and dbId='" + dbId + "' is not in NFS server cache"); + } + + return; + } + cachedNodes.put(nodeRef, dbId); + + String oldName = DefaultTypeConverter.INSTANCE.convert(String.class, before.get(ContentModel.PROP_NAME)); + String newName = DefaultTypeConverter.INSTANCE.convert(String.class, after.get(ContentModel.PROP_NAME)); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("oldName='" + oldName + "', newName='" + newName + "'"); + } + + if (((null == oldName) && (null != newName)) || ((null != oldName) && !oldName.equals(newName))) + { + String path = buildRelativePath(nodeRef, newName); + + updateNfsCache(nodeRef, path); + } + } + } + + @Override + public void beforeDeleteNode(NodeRef nodeRef) + { + if (enabled && (null != nfsServer) && (null != nodeRef) && targetStoreRef.equals(nodeRef.getStoreRef())) + { + int dbId = DefaultTypeConverter.INSTANCE.intValue(nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID)); + cachedNodes.put(nodeRef, dbId); + } + } + + @Override + public void onDeleteNode(ChildAssociationRef childAssocRef, boolean isNodeArchived) + { + NodeRef nodeRef = (null != childAssocRef) ? (childAssocRef.getChildRef()) : (null); + if (enabled && (null != nfsServer) && (null != nodeRef) && targetStoreRef.equals(nodeRef.getStoreRef())) + { + updateNfsCache(nodeRef, null); + } + } + + /** + * Searches for {@link ShareDetails} to access NFS server cache for specific device name (e.g. 'Alfresco', 'AVM' etc) + * + * @param fileId - {@link Integer} value which contains fileId specific to device + * @return {@link ShareDetails} instance which contains fileId key in the cache or null if such instance was not found + */ + private ShareDetails findShareDetailsForId(int fileId) + { + if ((null == nfsServer) || (null == nfsServer.getShareDetails())) + { + return null; + } + + Hashtable details = nfsServer.getShareDetails().getShareDetails(); + for (Integer key : details.keySet()) + { + ShareDetails shareDetails = details.get(key); + if (null != shareDetails.getFileIdCache().findPath(fileId)) + { + return shareDetails; + } + } + + return null; + } + + /** + * Builds path relative to NFS device name e.g. for the nfs.domain.name:/DeviceName/folder/document.doc method will return \folder\document.doc + * + * @param nodeRef - {@link NodeRef} instance for target node + * @param newName - {@link String} value which contains new name for the node + * @return {@link String} value of relative path + */ + private String buildRelativePath(NodeRef nodeRef, String newName) + { + String result = null; + Path path = nodeService.getPath(nodeRef); + if (null != path) + { + StringBuilder newPath = new StringBuilder(path.toDisplayPath(nodeService, permissionService)); + if ((NIX_SEPARATOR == newPath.charAt(0)) || (FileName.DOS_SEPERATOR == newPath.charAt(0))) + { + newPath.delete(0, 1); + } + int indexOfFirstSeparator = newPath.indexOf(NIX_SEPARATOR_STR); + indexOfFirstSeparator = (-1 == indexOfFirstSeparator) ? (newPath.indexOf(FileName.DOS_SEPERATOR_STR)) : (indexOfFirstSeparator); + char lastChar = newPath.charAt(newPath.length() - 1); + if ((FileName.DOS_SEPERATOR != lastChar) && (NIX_SEPARATOR != lastChar)) + { + newPath.append(FileName.DOS_SEPERATOR); + } + if (-1 == indexOfFirstSeparator) + { + indexOfFirstSeparator = newPath.length() - 1; + } + newPath = newPath.append(newName).delete(0, indexOfFirstSeparator); + result = newPath.toString().replace(NIX_SEPARATOR, FileName.DOS_SEPERATOR); + } + return result; + } + + /** + * Updates NFS cache for specified node. newPath equal to null determines that node should be deleted from the cache + * + * @param nodeRef - {@link NodeRef} value of the target node + * @param newPath - {@link String} value or null to determine new cache value for specified node + */ + private void updateNfsCache(NodeRef nodeRef, String newPath) + { + int dbId = -1; + if (cachedNodes.containsKey(nodeRef)) + { + dbId = (null != cachedNodes.get(nodeRef)) ? (cachedNodes.get(nodeRef)) : (-1); + cachedNodes.remove(nodeRef); + } + else + { + if (nodeService.exists(nodeRef)) + { + dbId = DefaultTypeConverter.INSTANCE.intValue(nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID)); + } + } + + ShareDetails shareDetails = findShareDetailsForId(dbId); + if (null != shareDetails) + { + if (null != newPath) + { + shareDetails.getFileIdCache().addPath(dbId, newPath); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Path='" + newPath + "' in cache was set for NodeRef='" + nodeRef + "', dbId ='" + dbId + "'"); + } + } + else + { + shareDetails.getFileIdCache().deletePath(dbId); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Cache field for node with NodeRef='" + nodeRef + "', dbId='" + dbId + "' was removed"); + } + } + } + } + + @Override + public boolean equals(Object obj) + { + if (obj instanceof NfsServerNodeMonitor) + { + NfsServerNodeMonitor converted = (NfsServerNodeMonitor) obj; + return areEqual(targetDeviceName, converted.getTargetDeviceName()) && areEqual(targetStoreRef, converted.getTargetStoreRef()); + } + return false; + } + + private boolean areEqual(Object left, Object right) + { + return (null != left) ? (left.equals(right)) : (null == right); + } + + @Override + public int hashCode() + { + int result = (null != targetDeviceName) ? (targetDeviceName.hashCode()) : (31); + return result * 37 + ((null != targetStoreRef) ? (targetStoreRef.hashCode()) : (43)); + } +} diff --git a/source/java/org/alfresco/filesys/ServerConfigurationBean.java b/source/java/org/alfresco/filesys/ServerConfigurationBean.java index e48eeb9cf0..21d6962aef 100644 --- a/source/java/org/alfresco/filesys/ServerConfigurationBean.java +++ b/source/java/org/alfresco/filesys/ServerConfigurationBean.java @@ -1414,17 +1414,6 @@ public class ServerConfigurationBean extends AbstractServerConfigurationBean { ftpConfig.setFTPDebug(ftpDbg); } - // Check if IPv6 support should be enabled - - elem = config.getConfigElement("IPv6"); - if ( elem != null) { - - // Enable IPv6 support - - if ( elem.hasAttribute("state") && elem.getAttribute("state").equalsIgnoreCase("enabled")) - ftpConfig.setIPv6Enabled( true); - } - // Check if a character set has been specified elem = config.getConfigElement( "charSet"); diff --git a/source/java/org/alfresco/filesys/alfresco/AlfrescoDiskDriver.java b/source/java/org/alfresco/filesys/alfresco/AlfrescoDiskDriver.java index 2965e45f52..5376bfbcd3 100644 --- a/source/java/org/alfresco/filesys/alfresco/AlfrescoDiskDriver.java +++ b/source/java/org/alfresco/filesys/alfresco/AlfrescoDiskDriver.java @@ -158,6 +158,9 @@ public abstract class AlfrescoDiskDriver implements IOCtlInterface, Transactiona /** * Perform a retryable operation in a write transaction + *

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

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

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

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

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

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

Quota manager implementation for the Alfresco repository. + *

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

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

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

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

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

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

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

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

C: 2 list "" ""

S: * LIST () "." ""

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

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

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

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

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

+ * Two rules:

    + *
  • When items are deleted, copy to another folder.
  • + *
  • In addition, when items are updated, add an aspect (or any other rule).
+ * Ensure that the first copy does not result in rules being fired on the target. + */ + public void testUpdateAndMoveRuleOnSameFolder() throws Exception + { + NodeRef sourceFolder = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}sourceFolder"), + ContentModel.TYPE_FOLDER).getChildRef(); + NodeRef targetFolder = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}targetFolder"), + ContentModel.TYPE_FOLDER).getChildRef(); + + // Create UPDATE rule to add lockable aspect + Map params = new HashMap(1); + params.put("aspect-name", ContentModel.ASPECT_LOCKABLE); + Rule rule = createRule( + RuleType.UPDATE, + AddFeaturesActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + this.ruleService.saveRule(sourceFolder, rule); + + // Check that the UPDATE rule works + NodeRef testNodeOneRef = fileFolderService.create(sourceFolder, "one.txt", ContentModel.TYPE_CONTENT).getNodeRef(); + assertFalse( + "Node should not have lockable aspect", + nodeService.hasAspect(testNodeOneRef, ContentModel.ASPECT_LOCKABLE)); + nodeService.setProperty(testNodeOneRef, ContentModel.PROP_LOCALE, Locale.CANADA); + assertTrue( + "Node should have lockable aspect", + nodeService.hasAspect(testNodeOneRef, ContentModel.ASPECT_LOCKABLE)); + fileFolderService.delete(testNodeOneRef); + + // Create OUTBOUND rule to copy node being deleted + params = new HashMap(1); + params.put(CopyActionExecuter.PARAM_DESTINATION_FOLDER, targetFolder); + Rule copyRule = createRule( + RuleType.OUTBOUND, + CopyActionExecuter.NAME, + params, + NoConditionEvaluator.NAME, + null); + copyRule.applyToChildren(true); + this.ruleService.saveRule(sourceFolder, copyRule); + + // Check that this OUTBOUND rule works + NodeRef testNodeTwoRef = fileFolderService.create(sourceFolder, "two.txt", ContentModel.TYPE_CONTENT).getNodeRef(); + assertFalse( + "Node should not have lockable aspect", + nodeService.hasAspect(testNodeTwoRef, ContentModel.ASPECT_LOCKABLE)); + fileFolderService.delete(testNodeTwoRef); + assertFalse("Node was not deleted", fileFolderService.exists(testNodeTwoRef)); + assertEquals( + "There should not be any children in source folder", + 0, + fileFolderService.listFiles(sourceFolder).size()); + List targetFolderFileList = fileFolderService.listFiles(targetFolder); + assertEquals( + "Node should have been copied to target folder", + 1, + targetFolderFileList.size()); + assertFalse( + "The node copy should not be lockable", + nodeService.hasAspect(targetFolderFileList.get(0).getNodeRef(), ContentModel.ASPECT_LOCKABLE)); } public void testDisableIndividualRules() @@ -531,7 +640,7 @@ public class RuleServiceCoverageTest extends TestCase addContentToNode(contentToCopy); Map params = new HashMap(1); - params.put("aspect-name", ContentModel.ASPECT_TEMPLATABLE); + params.put(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_TEMPLATABLE); Rule rule = createRule( RuleType.INBOUND, @@ -795,8 +904,6 @@ public class RuleServiceCoverageTest extends TestCase { Map params = new HashMap(1); params.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, this.rootNodeRef); - params.put(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CHILDREN); - params.put(MoveActionExecuter.PARAM_ASSOC_QNAME, QName.createQName(TEST_NAMESPACE, "copy")); Rule rule = createRule( RuleType.INBOUND, @@ -830,7 +937,7 @@ public class RuleServiceCoverageTest extends TestCase // Check that the created node has been copied List copyChildAssocRefs = this.nodeService.getChildAssocs( this.rootNodeRef, - RegexQNamePattern.MATCH_ALL, QName.createQName(TEST_NAMESPACE, "copy")); + RegexQNamePattern.MATCH_ALL, QName.createQName(TEST_NAMESPACE, "origional")); assertNotNull(copyChildAssocRefs); // ********************************** @@ -1014,8 +1121,6 @@ public class RuleServiceCoverageTest extends TestCase { Map params = new HashMap(1); params.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, this.rootNodeRef); - params.put(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CHILDREN); - params.put(MoveActionExecuter.PARAM_ASSOC_QNAME, QName.createQName(TEST_NAMESPACE, "copy")); Rule rule = createRule( RuleType.INBOUND, @@ -1046,7 +1151,7 @@ public class RuleServiceCoverageTest extends TestCase // Check that the created node is in the new location List copyChildAssocRefs = this.nodeService.getChildAssocs( this.rootNodeRef, - RegexQNamePattern.MATCH_ALL, QName.createQName(TEST_NAMESPACE, "copy")); + RegexQNamePattern.MATCH_ALL, QName.createQName(TEST_NAMESPACE, "origional")); assertNotNull(copyChildAssocRefs); assertEquals(1, copyChildAssocRefs.size()); NodeRef movedNodeRef = copyChildAssocRefs.get(0).getChildRef(); @@ -1122,7 +1227,6 @@ public class RuleServiceCoverageTest extends TestCase * condition: no-condition() * action: checkin() */ - @SuppressWarnings("unchecked") public void testCheckInAction() { Map params = new HashMap(1); diff --git a/source/java/org/alfresco/repo/rule/RuleServiceImpl.java b/source/java/org/alfresco/repo/rule/RuleServiceImpl.java index 50c6320abc..9ecdd1780e 100644 --- a/source/java/org/alfresco/repo/rule/RuleServiceImpl.java +++ b/source/java/org/alfresco/repo/rule/RuleServiceImpl.java @@ -35,6 +35,7 @@ import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.TransactionListener; import org.alfresco.service.cmr.action.Action; @@ -472,40 +473,47 @@ public class RuleServiceImpl /** * @see org.alfresco.repo.rule.RuleService#getRulesByRuleType(org.alfresco.repo.ref.NodeRef, org.alfresco.repo.rule.RuleType) */ - public List getRules(NodeRef nodeRef, boolean includeInherited, String ruleTypeName) + public List getRules(final NodeRef nodeRef, final boolean includeInherited, final String ruleTypeName) { - List rules = new ArrayList(); + //Run from system user: https://issues.alfresco.com/jira/browse/ALF-607 + return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork>() + { - if (!this.runtimeNodeService.exists(nodeRef) || !checkNodeType(nodeRef)) - { - // Node has gone or is not the correct type - return rules; - } - if (includeInherited == true && this.runtimeNodeService.hasAspect(nodeRef, RuleModel.ASPECT_IGNORE_INHERITED_RULES) == false) - { - // Get any inherited rules - for (Rule rule : getInheritedRules(nodeRef, ruleTypeName, null)) + public List doWork() throws Exception { - // Ensure rules are not duplicated in the list - if (rules.contains(rule) == false) + List rules = new ArrayList(); + + if (!runtimeNodeService.exists(nodeRef) || !checkNodeType(nodeRef)) { - rules.add(rule); + // Node has gone or is not the correct type + return rules; + } + if (includeInherited == true && runtimeNodeService.hasAspect(nodeRef, RuleModel.ASPECT_IGNORE_INHERITED_RULES) == false) + { + // Get any inherited rules + for (Rule rule : getInheritedRules(nodeRef, ruleTypeName, null)) + { + // Ensure rules are not duplicated in the list + if (rules.contains(rule) == false) + { + rules.add(rule); + } + } } - } - } - // Get the node's own rules and add them to the list - List nodeRules = getRulesForNode(nodeRef); - for (Rule rule : nodeRules) - { - if ((rules.contains(rule) == false) && - (ruleTypeName == null || rule.getRuleTypes().contains(ruleTypeName) == true)) - { - rules.add(rule); - } - } + // Get the node's own rules and add them to the list + List nodeRules = getRulesForNode(nodeRef); + for (Rule rule : nodeRules) + { + if ((rules.contains(rule) == false) && (ruleTypeName == null || rule.getRuleTypes().contains(ruleTypeName) == true)) + { + rules.add(rule); + } + } - return rules; + return rules; + } + }, AuthenticationUtil.getSystemUserName()); } private List getRulesForNode(NodeRef nodeRef) @@ -1162,9 +1170,17 @@ public class RuleServiceImpl } } - //update all associations and actions - rule = getRule(ruleNodeRef); - if (executedRules == null || canExecuteRule(executedRules, actionedUponNodeRef, rule) == true) + final NodeRef finalRuleNodeRef = ruleNodeRef; + // update all associations and actions + rule = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public Rule doWork() throws Exception + { + return getRule(finalRuleNodeRef); + } + }, AuthenticationUtil.getSystemUserName()); + + if (executedRules == null || canExecuteRule(executedRules, actionedUponNodeRef, rule) == true) { executeRule(rule, actionedUponNodeRef, executedRules); } @@ -1423,17 +1439,26 @@ public class RuleServiceImpl /** * @see org.alfresco.service.cmr.rule.RuleService#getOwningNodeRef(org.alfresco.service.cmr.rule.Rule) */ - public NodeRef getOwningNodeRef(Rule rule) + public NodeRef getOwningNodeRef(final Rule rule) { - NodeRef result = null; - - NodeRef ruleNodeRef = rule.getNodeRef(); - if (ruleNodeRef != null) + // Run from system user: https://issues.alfresco.com/jira/browse/ALF-607 + return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() { - result = getOwningNodeRefRuleImpl(ruleNodeRef); - } + + public NodeRef doWork() throws Exception + { + + NodeRef result = null; - return result; + NodeRef ruleNodeRef = rule.getNodeRef(); + if (ruleNodeRef != null) + { + result = getOwningNodeRefRuleImpl(ruleNodeRef); + } + + return result; + } + }, AuthenticationUtil.getSystemUserName()); } /** @@ -1452,16 +1477,25 @@ public class RuleServiceImpl /** * @see org.alfresco.service.cmr.rule.RuleService#getOwningNodeRef(org.alfresco.service.cmr.action.Action) */ - public NodeRef getOwningNodeRef(Action action) + public NodeRef getOwningNodeRef(final Action action) { - NodeRef result = null; - NodeRef actionNodeRef = action.getNodeRef(); - if (actionNodeRef != null) + // Run from system user: https://issues.alfresco.com/jira/browse/ALF-607 + return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() { - result = getOwningNodeRefActionImpl(actionNodeRef); - } + + public NodeRef doWork() throws Exception + { + + NodeRef result = null; + NodeRef actionNodeRef = action.getNodeRef(); + if (actionNodeRef != null) + { + result = getOwningNodeRefActionImpl(actionNodeRef); + } - return result; + return result; + } + }, AuthenticationUtil.getSystemUserName()); } /** diff --git a/source/java/org/alfresco/repo/rule/RuleServiceImplTest.java b/source/java/org/alfresco/repo/rule/RuleServiceImplTest.java index 88461c0704..2cd7aa0abd 100644 --- a/source/java/org/alfresco/repo/rule/RuleServiceImplTest.java +++ b/source/java/org/alfresco/repo/rule/RuleServiceImplTest.java @@ -21,12 +21,15 @@ package org.alfresco.repo.rule; import java.io.File; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import org.alfresco.model.ContentModel; import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; +import org.alfresco.repo.action.executer.AddFeaturesActionExecuter; import org.alfresco.repo.action.executer.ImageTransformActionExecuter; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.transform.AbstractContentTransformerTest; @@ -48,6 +51,7 @@ import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.GUID; /** @@ -1000,4 +1004,69 @@ public class RuleServiceImplTest extends BaseRuleTest } }, false, true); } + + public void testPermissionsForPropagatedRules_ALF_8408() throws Exception + { + // Create parent and child folders + NodeRef parentNodeRef = this.nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("parentnode" + GUID.generate()), ContentModel.TYPE_FOLDER) + .getChildRef(); + + NodeRef childNodeRef = this.nodeService.createNode(parentNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("childnode" + GUID.generate()), ContentModel.TYPE_FOLDER) + .getChildRef(); + + // Remove all permissions for parent + permissionService.deletePermissions(parentNodeRef); + permissionService.setInheritParentPermissions(parentNodeRef, false); + + // Create test user + String username = "ruleTestUser" + GUID.generate(); + this.authenticationService.createAuthentication(username, "password".toCharArray()); + + // Set user permissions for child node + permissionService.deletePermissions(childNodeRef); + permissionService.setInheritParentPermissions(childNodeRef, false); + permissionService.setPermission(childNodeRef, username, PermissionService.CONTRIBUTOR, true); + + // Create rule for child node + Rule testRule = new Rule(); + testRule.setRuleTypes(Collections.singletonList(RuleType.INBOUND)); + testRule.setTitle("RuleServiceTest" + GUID.generate()); + testRule.setDescription(DESCRIPTION); + testRule.applyToChildren(true); + Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE); + testRule.setAction(action); + this.ruleService.saveRule(parentNodeRef, testRule); + assertNotNull("Rule was not saved", testRule.getNodeRef()); + + // Authenticate as test user + this.authenticationService.authenticate(username, "password".toCharArray()); + authenticationComponent.setCurrentUser(username); + + // Search rules + List rules = this.ruleService.getRules(childNodeRef, true, testRule.getRuleTypes().get(0)); + assertNotNull("No rules found", rules); + assertTrue("Created rule is not found", new HashSet(rules).contains(testRule)); + + // New node + NodeRef actionedUponNodeRef = this.nodeService.createNode(childNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("actioneduponnode" + GUID.generate()), + ContentModel.TYPE_CONTENT).getChildRef(); + + // Testing immediate rule execution + if (this.nodeService.hasAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE)) + { + this.nodeService.removeAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE); + } + ((RuntimeRuleService) ruleService).executeRule(testRule, actionedUponNodeRef, null); + assertTrue("Rule was not executed", this.nodeService.hasAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE)); + + // Queue the rule to be executed later and execute pending rules + if (this.nodeService.hasAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE)) + { + this.nodeService.removeAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE); + } + ((RuntimeRuleService) ruleService).addRulePendingExecution(parentNodeRef, actionedUponNodeRef, testRule); + ((RuntimeRuleService) ruleService).executePendingRules(); + assertTrue("Pending rule was not executed", this.nodeService.hasAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE)); + } } diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/BeforeDeleteChildAssociationRuleTrigger.java b/source/java/org/alfresco/repo/rule/ruletrigger/BeforeDeleteChildAssociationRuleTrigger.java index fa5f936fdf..43ea840b44 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/BeforeDeleteChildAssociationRuleTrigger.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/BeforeDeleteChildAssociationRuleTrigger.java @@ -18,9 +18,13 @@ */ package org.alfresco.repo.rule.ruletrigger; +import java.util.Set; + import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.transaction.TransactionalResourceHelper; import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; @@ -78,11 +82,20 @@ public class BeforeDeleteChildAssociationRuleTrigger public void beforeDeleteChildAssociation(ChildAssociationRef childAssocRef) { + NodeRef childNodeRef = childAssocRef.getChildRef(); + + // Avoid renamed nodes + Set renamedNodeRefSet = TransactionalResourceHelper.getSet(RULE_TRIGGER_RENAMED_NODES); + if (renamedNodeRefSet.contains(childNodeRef)) + { + return; + } + if (logger.isDebugEnabled() == true) { logger.debug("Single child assoc trigger (policy = " + POLICY + ") fired for parent node " + childAssocRef.getParentRef() + " and child node " + childAssocRef.getChildRef()); } - triggerRules(childAssocRef.getParentRef(), childAssocRef.getChildRef()); + triggerRules(childAssocRef.getParentRef(), childNodeRef); } } diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/CreateNodeRuleTrigger.java b/source/java/org/alfresco/repo/rule/ruletrigger/CreateNodeRuleTrigger.java index 1dd2bbe2d6..23de4a3c76 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/CreateNodeRuleTrigger.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/CreateNodeRuleTrigger.java @@ -18,10 +18,13 @@ */ package org.alfresco.repo.rule.ruletrigger; +import java.util.Set; + import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.Behaviour.NotificationFrequency; import org.alfresco.repo.rule.RuntimeRuleService; +import org.alfresco.repo.transaction.TransactionalResourceHelper; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.NamespaceService; @@ -109,8 +112,13 @@ public class CreateNodeRuleTrigger extends RuleTriggerAbstractBase * {@inheritDoc} */ public void onCreateNode(ChildAssociationRef childAssocRef) - { + { NodeRef nodeRef = childAssocRef.getChildRef(); + + // Keep track of new nodes to prevent firing of updates in the same transaction + Set newNodeRefSet = TransactionalResourceHelper.getSet(RULE_TRIGGER_NEW_NODES); + newNodeRefSet.add(nodeRef); + if (nodeRef != null && nodeService.exists(nodeRef) == true && nodeService.hasAspect(nodeRef, ASPECT_NO_CONTENT) == false) diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/OnContentUpdateRuleTrigger.java b/source/java/org/alfresco/repo/rule/ruletrigger/OnContentUpdateRuleTrigger.java index 42910cb045..74dbac355a 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/OnContentUpdateRuleTrigger.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/OnContentUpdateRuleTrigger.java @@ -19,11 +19,13 @@ package org.alfresco.repo.rule.ruletrigger; import java.util.List; +import java.util.Set; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.ContentServicePolicies; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.transaction.TransactionalResourceHelper; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.NodeRef; @@ -112,6 +114,18 @@ public class OnContentUpdateRuleTrigger extends RuleTriggerAbstractBase } } } + + // Double check for content created in this transaction + if (fail == false && !newContent) + { + Set newNodeRefSet = TransactionalResourceHelper.getSet(RULE_TRIGGER_NEW_NODES); + boolean wasCreatedInTxn = newNodeRefSet.contains(nodeRef); + if (logger.isDebugEnabled() && wasCreatedInTxn) + { + logger.debug("Receiving content property update for node created in transaction: " + nodeRef); + } + fail = wasCreatedInTxn; + } // Trigger the rules in the appropriate way if (fail == false && newContent == this.onNewContent) diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/OnCreateChildAssociationRuleTrigger.java b/source/java/org/alfresco/repo/rule/ruletrigger/OnCreateChildAssociationRuleTrigger.java index 2344831b1e..96596e760b 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/OnCreateChildAssociationRuleTrigger.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/OnCreateChildAssociationRuleTrigger.java @@ -24,6 +24,7 @@ import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.transaction.TransactionalResourceHelper; import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; @@ -80,31 +81,26 @@ public class OnCreateChildAssociationRuleTrigger public void onCreateChildAssociation(ChildAssociationRef childAssocRef, boolean isNewNode) { + // Avoid new nodes if (isNewNode) { return; } + + NodeRef childNodeRef = childAssocRef.getChildRef(); + + // Avoid renamed nodes + Set renamedNodeRefSet = TransactionalResourceHelper.getSet(RULE_TRIGGER_RENAMED_NODES); + if (renamedNodeRefSet.contains(childNodeRef)) + { + return; + } + if (logger.isDebugEnabled() == true) { logger.debug("Single child assoc trigger (policy = " + POLICY_NAME + ") fired for parent node " + childAssocRef.getParentRef() + " and child node " + childAssocRef.getChildRef()); } - // NOTE: - // - // We check for the presence of this resource in the transaction to determine whether a rename has been issued. If that is the case - // then we don't want to trigger any associated rules. - // - // See http://issues.alfresco.com/browse/AR-1544 - Set nodeRefRenameSet = TransactionalResourceHelper.getSet(RULE_TRIGGER_NODESET); - String marker = childAssocRef.getChildRef().toString()+"rename"; - if (!nodeRefRenameSet.contains(marker)) - { - triggerRules(childAssocRef.getParentRef(), childAssocRef.getChildRef()); - } - else - { - // Remove the marker - nodeRefRenameSet.remove(marker); - } + triggerRules(childAssocRef.getParentRef(), childNodeRef); } } diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/OnPropertyUpdateRuleTrigger.java b/source/java/org/alfresco/repo/rule/ruletrigger/OnPropertyUpdateRuleTrigger.java index de62be9923..a1f6084da9 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/OnPropertyUpdateRuleTrigger.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/OnPropertyUpdateRuleTrigger.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.alfresco.model.ContentModel; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.transaction.TransactionalResourceHelper; @@ -120,37 +121,54 @@ public class OnPropertyUpdateRuleTrigger extends RuleTriggerAbstractBase } /** - * @see org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy#onUpdateProperties(org.alfresco.service.cmr.repository.NodeRef, java.util.Map, java.util.Map) + * Triggers rules if properties have been updated */ public void onUpdateProperties(NodeRef nodeRef, Map before, Map after) { - if (logger.isDebugEnabled() == true) + // Do not fire if the node has been created in this transaction + Set newNodeRefSet = TransactionalResourceHelper.getSet(RULE_TRIGGER_NEW_NODES); + boolean wasCreatedInTxn = newNodeRefSet.contains(nodeRef); + if (logger.isDebugEnabled() && wasCreatedInTxn) { - logger.debug("OnPropertyUpdate rule triggered fired; nodeRef=" + nodeRef.toString() + "; triggerParentRules=" + this.triggerParentRules); + logger.debug("Receiving property update for node created in transaction: " + nodeRef); } - Set nodeRefSet = TransactionalResourceHelper.getSet(RULE_TRIGGER_NODESET); - // Only try and trigger the rules if a non protected property has been modified - if (!nodeRefSet.contains(nodeRef.toString()) && + if (!wasCreatedInTxn && before.size() != 0 && // ALF-4846: Do not trigger for newly created nodes havePropertiesBeenModified(nodeRef, before, after) == true) { + // Keep track of name changes explicitly. This prevents the later association change from + // triggering 'inbound' rules + if (!EqualsHelper.nullSafeEquals(before.get(ContentModel.PROP_NAME), after.get(ContentModel.PROP_NAME))) + { + // Name has changed + Set renamedNodeRefSet = TransactionalResourceHelper.getSet(RULE_TRIGGER_RENAMED_NODES); + renamedNodeRefSet.add(nodeRef); + } + if (triggerParentRules == true) { List parentsAssocRefs = this.nodeService.getParentAssocs(nodeRef); for (ChildAssociationRef parentAssocRef : parentsAssocRefs) { triggerRules(parentAssocRef.getParentRef(), nodeRef); + if (logger.isDebugEnabled() == true) + { + logger.debug( + "OnPropertyUpdate rule triggered (parent); " + + "nodeRef=" + parentAssocRef.getParentRef()); + } } } else { triggerRules(nodeRef, nodeRef); + if (logger.isDebugEnabled() == true) + { + logger.debug("OnPropertyUpdate rule triggered; nodeRef=" + nodeRef); + } } } } - - - } diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/RuleTrigger.java b/source/java/org/alfresco/repo/rule/ruletrigger/RuleTrigger.java index a295b5e5df..31e8e83aec 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/RuleTrigger.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/RuleTrigger.java @@ -27,8 +27,10 @@ import org.alfresco.service.cmr.rule.RuleType; */ public interface RuleTrigger { - /** Key to look up a Set of String values controlling the firing of rules */ - public static final String RULE_TRIGGER_NODESET = "RuleTrigger.NodeSet"; + /** Key to store newly-created nodes for the controlling of rule triggers */ + public static final String RULE_TRIGGER_NEW_NODES = "RuleTrigger.NewNodes"; + /** Key to store renamed nodes for the controlling of rule triggers */ + public static final String RULE_TRIGGER_RENAMED_NODES = "RuleTrigger.RenamedNodes"; /** * Register the rule trigger diff --git a/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerTest.java b/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerTest.java index 19e47c1571..cf3a995025 100644 --- a/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerTest.java +++ b/source/java/org/alfresco/repo/rule/ruletrigger/RuleTriggerTest.java @@ -20,6 +20,8 @@ package org.alfresco.repo.rule.ruletrigger; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; @@ -53,14 +55,23 @@ public class RuleTriggerTest extends BaseSpringTest @Override protected void onSetUpInTransaction() throws Exception { - this.nodeService = (NodeService)this.applicationContext.getBean("nodeService"); - this.contentService = (ContentService)this.applicationContext.getBean("contentService"); + ServiceRegistry serviceRegistry = (ServiceRegistry) applicationContext.getBean(ServiceRegistry.SERVICE_REGISTRY); + this.nodeService = serviceRegistry.getNodeService(); + this.contentService = serviceRegistry.getContentService(); + + AuthenticationUtil.setRunAsUser(AuthenticationUtil.getSystemUserName()); this.testStoreRef = this.nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); this.rootNodeRef = this.nodeService.getRootNode(this.testStoreRef); } - public void testOnCreateNodeTrigger() + @Override + protected void onTearDownInTransaction() throws Exception + { + AuthenticationUtil.clearCurrentSecurityContext(); + } + + public void testOnCreateNodeTrigger() { TestRuleType ruleType = createTestRuleType(ON_CREATE_NODE_TRIGGER); assertFalse(ruleType.rulesTriggered); @@ -233,6 +244,17 @@ public class RuleTriggerTest extends BaseSpringTest // Check to see if the rule type has been triggered assertTrue(contentCreate.rulesTriggered); + + // Try and trigger the type (again) + contentCreate.rulesTriggered = false; + assertFalse(contentCreate.rulesTriggered); + ContentWriter contentWriter2 = this.contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); + contentWriter2.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + contentWriter2.setEncoding("UTF-8"); + contentWriter2.putContent("some content"); + + // Check to see if the rule type has been triggered + assertFalse(contentCreate.rulesTriggered); } public void testOnContentUpdateTrigger() @@ -243,7 +265,9 @@ public class RuleTriggerTest extends BaseSpringTest ContentModel.ASSOC_CHILDREN, ContentModel.TYPE_CONTENT).getChildRef(); + TestRuleType contentCreate = createTestRuleType(ON_CONTENT_CREATE_TRIGGER); TestRuleType contentUpdate = createTestRuleType(ON_CONTENT_UPDATE_TRIGGER); + assertFalse(contentCreate.rulesTriggered); assertFalse(contentUpdate.rulesTriggered); // Try and trigger the type @@ -252,17 +276,41 @@ public class RuleTriggerTest extends BaseSpringTest contentWriter.setEncoding("UTF-8"); contentWriter.putContent("some content"); - // Check to see if the rule type has been triggered + // Check to see if the rule type has been triggered + assertTrue(contentCreate.rulesTriggered); assertFalse(contentUpdate.rulesTriggered); - // Try and trigger the type + // Try and trigger the type (again) + contentCreate.rulesTriggered = false; + assertFalse(contentCreate.rulesTriggered); ContentWriter contentWriter2 = this.contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); contentWriter2.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); contentWriter2.setEncoding("UTF-8"); contentWriter2.putContent("more content some content"); // Check to see if the rule type has been triggered - assertTrue(contentUpdate.rulesTriggered); + assertFalse(contentCreate.rulesTriggered); + assertFalse( + "Content update must not fire if the content was created in the same txn.", + contentUpdate.rulesTriggered); + + // Terminate the transaction + setComplete(); + endTransaction(); + + // Try and trigger the type (again) + ContentWriter contentWriter3 = this.contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); + contentWriter3.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + contentWriter3.setEncoding("UTF-8"); + contentWriter3.putContent("Yet content some content"); + + // Check to see if the rule type has been triggered + assertFalse( + "Content create should not be fired on an update in a new txn", + contentCreate.rulesTriggered); + assertTrue( + "Content update must not fire if the content was created in the same txn.", + contentUpdate.rulesTriggered); } private TestRuleType createTestRuleType(String ruleTriggerName) diff --git a/source/java/org/alfresco/repo/search/AVMSnapShotTriggeredIndexingMethodInterceptor.java b/source/java/org/alfresco/repo/search/AVMSnapShotTriggeredIndexingMethodInterceptor.java index ae822d1414..4f391715b8 100644 --- a/source/java/org/alfresco/repo/search/AVMSnapShotTriggeredIndexingMethodInterceptor.java +++ b/source/java/org/alfresco/repo/search/AVMSnapShotTriggeredIndexingMethodInterceptor.java @@ -271,6 +271,8 @@ public class AVMSnapShotTriggeredIndexingMethodInterceptor implements MethodInte if ((last == -1) && (! hasIndexBeenCreated(store))) { createIndex(store); + // ALF-7845 + last = getLastIndexedSnapshot(avmIndexer, store); } int from = before != -1 ? before : last; diff --git a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java index 414843e7bb..d5889087f8 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java @@ -219,6 +219,7 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp NodeRef ref = new NodeRef(id); deleteImpl(ref.toString(), IndexDeleteMode.DELETE, true, mainReader); } + td.close(); } catch (IOException e) { @@ -1083,7 +1084,7 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp } // locale free identifiers are in the default field - doc.add(new Field(attributeName, t.termText(), Field.Store.NO, Field.Index.NO_NORMS, Field.TermVector.NO)); + doc.add(new Field(attributeName, t.termText(), fieldStore, Field.Index.NO_NORMS, Field.TermVector.NO)); } } @@ -1195,7 +1196,7 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp { doc.add(new Field(attributeName + "." + localeText + ".sort", t.termText(), Field.Store.NO, Field.Index.NO_NORMS, Field.TermVector.NO)); } - doc.add(new Field(attributeName, t.termText(), Field.Store.NO, Field.Index.NO_NORMS, Field.TermVector.NO)); + doc.add(new Field(attributeName, t.termText(), fieldStore, Field.Index.NO_NORMS, Field.TermVector.NO)); } } catch (IOException e) @@ -1265,7 +1266,7 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp try { date = df.parse(strValue); - doc.add(new Field(attributeName, df.format(date), Field.Store.NO, Field.Index.NO_NORMS, Field.TermVector.NO)); + doc.add(new Field(attributeName, df.format(date), fieldStore, Field.Index.NO_NORMS, Field.TermVector.NO)); } catch (ParseException e) { diff --git a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java index 136f3e3ac8..b6277a03e8 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java @@ -103,6 +103,10 @@ import org.alfresco.util.ISO9075; import org.alfresco.util.CachingDateFormat.SimpleDateFormatAndResolution; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.Term; +import org.apache.lucene.index.TermDocs; +import org.apache.lucene.index.TermEnum; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.PostgreSQLDialect; import org.springframework.context.ApplicationContext; @@ -216,9 +220,9 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener private QueryEngine queryEngine; private NodeRef n15; - + private M2Model model; - + // TODO: pending replacement private Dialect dialect; @@ -238,17 +242,14 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener super(arg0); } - public void afterDictionaryDestroy() { } - public void afterDictionaryInit() { } - public void onDictionaryInit() { // Register the test model @@ -256,11 +257,10 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener namespaceDao.addPrefix("test", TEST_NAMESPACE); } - public void setUp() throws Exception { dialect = (Dialect) ctx.getBean("dialect"); - + nodeService = (NodeService) ctx.getBean("dbNodeService"); dictionaryService = (DictionaryService) ctx.getBean("dictionaryService"); dictionaryDAO = (DictionaryDAO) ctx.getBean("dictionaryDAO"); @@ -297,11 +297,11 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener ClassLoader cl = BaseNodeServiceTest.class.getClassLoader(); InputStream modelStream = cl.getResourceAsStream("org/alfresco/repo/search/impl/lucene/LuceneTest_model.xml"); assertNotNull(modelStream); - model = M2Model.createModel(modelStream); + model = M2Model.createModel(modelStream); dictionaryDAO.register(this); dictionaryDAO.reset(); assertNotNull(dictionaryDAO.getClass(testSuperType)); - + StoreRef storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); rootNodeRef = nodeService.getRootNode(storeRef); @@ -482,7 +482,7 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener // note: cm:thumbnail - hence auditable aspect will be applied with mandatory properties (cm:created, cm:modified, cm:creator, cm:modifier) n15 = nodeService.createNode(n13, ASSOC_TYPE_QNAME, QName.createQName("{namespace}fifteen"), ContentModel.TYPE_THUMBNAIL, getOrderProperties()).getChildRef(); - + ContentWriter writer = contentService.getWriter(n14, ContentModel.PROP_CONTENT, true); writer.setEncoding("UTF-8"); // InputStream is = @@ -837,6 +837,168 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener } + private IndexReader getIndexReader() + { + ADMLuceneSearcherImpl searcher = ADMLuceneSearcherImpl.getSearcher(rootNodeRef.getStoreRef(), indexerAndSearcher); + searcher.setNodeService(nodeService); + searcher.setDictionaryService(dictionaryService); + searcher.setTenantService(tenantService); + searcher.setNamespacePrefixResolver(getNamespacePrefixResolver("namespace")); + searcher.setQueryRegister(queryRegisterComponent); + searcher.setQueryLanguages(((AbstractLuceneIndexerAndSearcherFactory) indexerAndSearcher).queryLanguages); + + return searcher.getSearcher().getIndexReader(); + } + + public void testMaskDeletes() throws Exception + { + testTX.commit(); + testTX = transactionService.getUserTransaction(); + testTX.begin(); + + SearchParameters sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery("PATH:\"//.\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(true); + ResultSet results = serviceRegistry.getSearchService().query(sp); + int initialCount = results.length(); + results.close(); + + for (int j = 0; j < 20; j++) + { + ArrayList added = new ArrayList(); + for (int i = 0; i < 50; i++) + { + Map properties = new HashMap(); + properties.put(ContentModel.PROP_NAME, "Mask " + i); + added.add(nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "mask-" + i), testSuperType, + properties).getChildRef()); + } + testTX.commit(); + testTX = transactionService.getUserTransaction(); + testTX.begin(); + + int count = 0; + IndexReader indexReader = getIndexReader(); + TermDocs termDocs = indexReader.termDocs(new Term("@{http://www.alfresco.org/model/content/1.0}name", "mask")); + if (termDocs.next()) + { + count++; + while (termDocs.skipTo(termDocs.doc())) + { + count++; + } + } + termDocs.close(); + assertEquals(added.size() + j, count); + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery("PATH:\"//cm:*\" AND @cm\\:name:(0 1 2 3 4 5 6 7 8 9) AND ISNOTNULL:\"cm:name\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(true); + + results = serviceRegistry.getSearchService().query(sp); + results.close(); + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery("@cm\\:name:\"mask 1\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(true); + + results = serviceRegistry.getSearchService().query(sp); + results.close(); + + for (int i = 0; i < added.size() - 1; i++) + { + Map properties = new HashMap(); + properties.put(ContentModel.PROP_NAME, "Mask " + i); + nodeService.setProperties(added.get(i), properties); + } + + testTX.commit(); + testTX = transactionService.getUserTransaction(); + testTX.begin(); + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery("PATH:\"//cm:*\" AND @cm\\:name:(0 1 2 3 4 5 6 7 8 9) AND ISNOTNULL:\"cm:name\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(true); + + results = serviceRegistry.getSearchService().query(sp); + results.close(); + + count = 0; + indexReader = getIndexReader(); + termDocs = indexReader.termDocs(new Term("@{http://www.alfresco.org/model/content/1.0}name", "mask")); + if (termDocs.next()) + { + count++; + while (termDocs.skipTo(termDocs.doc())) + { + count++; + } + } + termDocs.close(); + assertEquals(added.size() + j, count); + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery("@cm\\:name:\"mask 1\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(true); + + results = serviceRegistry.getSearchService().query(sp); + results.close(); + + for (int i = 0; i < added.size() - 1; i++) + { + Map properties = new HashMap(); + properties.put(ContentModel.PROP_NAME, "Mask " + i); + nodeService.deleteNode(added.get(i)); + } + testTX.commit(); + testTX = transactionService.getUserTransaction(); + testTX.begin(); + + count = 0; + indexReader = getIndexReader(); + termDocs = indexReader.termDocs(new Term("@{http://www.alfresco.org/model/content/1.0}name", "mask")); + if (termDocs.next()) + { + count++; + while (termDocs.skipTo(termDocs.doc())) + { + count++; + } + } + termDocs.close(); + assertEquals(j+1, count); + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery("PATH:\"//cm:*\" AND @cm\\:name:(0 1 2 3 4 5 6 7 8 9) AND ISNOTNULL:\"cm:name\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(true); + + results = serviceRegistry.getSearchService().query(sp); + results.close(); + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery("@cm\\:name:\"mask 1\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(true); + + results = serviceRegistry.getSearchService().query(sp); + results.close(); + } + + } + public void testQuoting() throws Exception { testTX.commit(); @@ -852,11 +1014,11 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener ResultSet results = serviceRegistry.getSearchService().query(sp); results.close(); } - + public void test_ALF_8007() throws Exception { // Check that updates before and after queries do not produce duplicates - + testTX.commit(); testTX = transactionService.getUserTransaction(); testTX.begin(); @@ -875,60 +1037,57 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results = serviceRegistry.getSearchService().query(sp); assertEquals(0, results.length()); results.close(); - - + Map properties = new HashMap(); properties.put(ContentModel.PROP_NAME, "ALF-8007"); NodeRef one = nodeService.createNode(rootNodeRef, ASSOC_TYPE_QNAME, QName.createQName("{namespace}ALF-8007"), ContentModel.TYPE_CONTENT, properties).getChildRef(); - + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007\""); sp.addStore(rootNodeRef.getStoreRef()); sp.excludeDataInTheCurrentTransaction(false); - + results = serviceRegistry.getSearchService().query(sp); assertEquals(1, results.length()); results.close(); - - + MLText desc1 = new MLText(); desc1.addValue(Locale.ENGLISH, "ALF 8007"); desc1.addValue(Locale.US, "ALF 8007"); - + nodeService.setProperty(one, ContentModel.PROP_DESCRIPTION, desc1); - + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007\""); sp.addStore(rootNodeRef.getStoreRef()); sp.excludeDataInTheCurrentTransaction(false); - + results = serviceRegistry.getSearchService().query(sp); assertEquals(1, results.length()); results.close(); - + // check delete after update does delete from the index // ALF-8007 // Already seen the delete in the TX and it is skipped (should only skip deletes in the same flush) - + nodeService.deleteNode(one); - + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007\""); sp.addStore(rootNodeRef.getStoreRef()); sp.excludeDataInTheCurrentTransaction(false); - + results = serviceRegistry.getSearchService().query(sp); assertEquals(0, results.length()); results.close(); - + // Check unreported ... create, query, update, delete - // ... create, query, move, delete - - + // ... create, query, move, delete + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007-2\""); @@ -938,11 +1097,11 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results = serviceRegistry.getSearchService().query(sp); assertEquals(0, results.length()); results.close(); - + properties = new HashMap(); properties.put(ContentModel.PROP_NAME, "ALF-8007-2"); NodeRef two = nodeService.createNode(rootNodeRef, ASSOC_TYPE_QNAME, QName.createQName("{namespace}ALF-8007-2"), ContentModel.TYPE_CONTENT, properties).getChildRef(); - + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007-2\""); @@ -952,26 +1111,26 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results = serviceRegistry.getSearchService().query(sp); assertEquals(1, results.length()); results.close(); - + desc1 = new MLText(); desc1.addValue(Locale.ENGLISH, "ALF 8007 2"); desc1.addValue(Locale.US, "ALF 8007 2"); - + nodeService.setProperty(two, ContentModel.PROP_DESCRIPTION, desc1); nodeService.deleteNode(two); - + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007-2\""); sp.addStore(rootNodeRef.getStoreRef()); sp.excludeDataInTheCurrentTransaction(false); - + results = serviceRegistry.getSearchService().query(sp); assertEquals(0, results.length()); results.close(); - - // ... create, query, move, delete - + + // ... create, query, move, delete + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007-3\""); @@ -981,11 +1140,11 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results = serviceRegistry.getSearchService().query(sp); assertEquals(0, results.length()); results.close(); - + properties = new HashMap(); properties.put(ContentModel.PROP_NAME, "ALF-8007-3"); NodeRef three = nodeService.createNode(rootNodeRef, ASSOC_TYPE_QNAME, QName.createQName("{namespace}ALF-8007-3"), ContentModel.TYPE_CONTENT, properties).getChildRef(); - + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007-3\""); @@ -995,26 +1154,26 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results = serviceRegistry.getSearchService().query(sp); assertEquals(1, results.length()); results.close(); - + desc1 = new MLText(); desc1.addValue(Locale.ENGLISH, "ALF 8007 3"); desc1.addValue(Locale.US, "ALF 8007 3"); - + nodeService.moveNode(three, n1, ASSOC_TYPE_QNAME, QName.createQName("{namespace}ALF-8007-3")); nodeService.deleteNode(three); - + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007-3\""); sp.addStore(rootNodeRef.getStoreRef()); sp.excludeDataInTheCurrentTransaction(false); - + results = serviceRegistry.getSearchService().query(sp); assertEquals(0, results.length()); results.close(); - - // ... create, move, query, delete - + + // ... create, move, query, delete + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007-4\""); @@ -1024,11 +1183,11 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results = serviceRegistry.getSearchService().query(sp); assertEquals(0, results.length()); results.close(); - + properties = new HashMap(); properties.put(ContentModel.PROP_NAME, "ALF-8007-4"); NodeRef four = nodeService.createNode(rootNodeRef, ASSOC_TYPE_QNAME, QName.createQName("{namespace}ALF-8007-4"), ContentModel.TYPE_CONTENT, properties).getChildRef(); - + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007-4\""); @@ -1038,13 +1197,13 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results = serviceRegistry.getSearchService().query(sp); assertEquals(1, results.length()); results.close(); - + desc1 = new MLText(); desc1.addValue(Locale.ENGLISH, "ALF 8007 4"); desc1.addValue(Locale.US, "ALF 8007 4"); - + nodeService.moveNode(four, n1, ASSOC_TYPE_QNAME, QName.createQName("{namespace}ALF-8007-4")); - + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007-4\""); @@ -1054,21 +1213,20 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results = serviceRegistry.getSearchService().query(sp); assertEquals(1, results.length()); results.close(); - + nodeService.deleteNode(four); - + sp = new SearchParameters(); sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); sp.setQuery("cm:name:\"ALF-8007-4\""); sp.addStore(rootNodeRef.getStoreRef()); sp.excludeDataInTheCurrentTransaction(false); - + results = serviceRegistry.getSearchService().query(sp); assertEquals(0, results.length()); results.close(); - - } + } public void testPublicServiceSearchServicePaging() throws Exception { @@ -1498,29 +1656,29 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener ftsQueryWithCount(searcher, "brown..dog", 1); // is this allowed?? fail("Range query should not be supported against type d:content"); } - catch(UnsupportedOperationException e) + catch (UnsupportedOperationException e) { - + } - + try { ftsQueryWithCount(searcher, "TEXT:brown..dog", 1); fail("Range query should not be supported against type d:content"); } - catch(UnsupportedOperationException e) + catch (UnsupportedOperationException e) { - + } - + try { ftsQueryWithCount(searcher, "cm:content:brown..dog", 1); fail("Range query should not be supported against type d:content"); } - catch(UnsupportedOperationException e) + catch (UnsupportedOperationException e) { - + } QName qname = QName.createQName(TEST_NAMESPACE, "float\\-ista"); @@ -1695,7 +1853,7 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener assertEquals(count, results.length()); results.close(); } - + public void ftsQueryWithCount(ADMLuceneSearcherImpl searcher, String defaultFieldName, String query, int count) { SearchParameters sp = new SearchParameters(); @@ -2654,7 +2812,7 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener { public Object execute() throws Throwable { - for (int i = 0; i < 100; i+=10) + for (int i = 0; i < 100; i += 10) { HashSet refs = new HashSet(); for (int j = 0; j < i; j++) @@ -3425,7 +3583,7 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener // sort by ML text - //Locale[] testLocales = new Locale[] { I18NUtil.getLocale(), Locale.ENGLISH, Locale.FRENCH, Locale.CHINESE }; + // Locale[] testLocales = new Locale[] { I18NUtil.getLocale(), Locale.ENGLISH, Locale.FRENCH, Locale.CHINESE }; Locale[] testLocales = new Locale[] { I18NUtil.getLocale(), Locale.ENGLISH, Locale.FRENCH }; for (Locale testLocale : testLocales) { @@ -3493,7 +3651,7 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results.close(); // test sort on unkown properties ALF-4193 - + spN = new SearchParameters(); spN.addStore(rootNodeRef.getStoreRef()); spN.setLanguage(SearchService.LANGUAGE_LUCENE); @@ -3501,7 +3659,7 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener spN.addSort("PARENT", false); results = searcher.query(spN); results.close(); - + spN = new SearchParameters(); spN.addStore(rootNodeRef.getStoreRef()); spN.setLanguage(SearchService.LANGUAGE_LUCENE); @@ -3510,7 +3668,6 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results = searcher.query(spN); results.close(); - luceneFTS.resume(); @@ -4462,7 +4619,7 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener continue; } System.out.println("Date format: "+df.getSimpleDateFormat()); - + // if(usesDateTimeAnalyser && (df.getSimpleDateFormat().format(date).length() < 22)) // { // continue; @@ -4508,7 +4665,7 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener assertTrue("n14 not in results", (results.getNodeRef(0).equals(n14) || results.getNodeRef(1).equals(n14))); assertTrue("n15 not in results", (results.getNodeRef(0).equals(n15) || results.getNodeRef(1).equals(n15))); results.close(); - + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@cm\\:created:[MIN TO NOW]", null); assertEquals(2, results.length()); @@ -4889,39 +5046,40 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TYPE:\"" + testSuperType.toPrefixString(namespacePrefixResolver) + "\"", null); assertEquals(13, results.length()); results.close(); - + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TYPE:\"" + ContentModel.TYPE_CONTENT.toString() + "\"", null); assertEquals(1, results.length()); results.close(); - + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TYPE:\"cm:content\"", null); assertEquals(1, results.length()); results.close(); - + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TYPE:\"cm:CONTENT\"", null); assertEquals(1, results.length()); results.close(); - + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TYPE:\"CM:CONTENT\"", null); assertEquals(1, results.length()); results.close(); - + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TYPE:\"CONTENT\"", null); assertEquals(1, results.length()); results.close(); - + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TYPE:\"content\"", null); assertEquals(1, results.length()); results.close(); - + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TYPE:\"" + ContentModel.TYPE_THUMBNAIL.toString() + "\"", null); assertEquals(1, results.length()); results.close(); - results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TYPE:\"" + ContentModel.TYPE_THUMBNAIL.toString() + "\" TYPE:\"" + ContentModel.TYPE_CONTENT.toString() + "\"", null); + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TYPE:\"" + + ContentModel.TYPE_THUMBNAIL.toString() + "\" TYPE:\"" + ContentModel.TYPE_CONTENT.toString() + "\"", null); assertEquals(2, results.length()); results.close(); - + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "EXACTTYPE:\"" + testSuperType.toString() + "\"", null); assertEquals(12, results.length()); results.close(); @@ -6610,7 +6768,7 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener // http://archives.postgresql.org/pgsql-jdbc/2007-02/msg00115.php COMPLEX_LOCAL_NAME = "\u0020\u0060\u00ac\u00a6\u0021\"\u00a3\u0024\u0025\u005e\u0026\u002a\u0028\u0029\u002d\u005f\u003d\u002b\t\n\\\u005b\u005d\u007b\u007d\u003b\u0027\u0023\u003a\u0040\u007e\u002c\u002e\u002f\u003c\u003e\u003f\\u007c\u005f\u0078\u0054\u0036\u0035\u0041\u005f"; } - + luceneFTS.pause(); buildBaseIndex(); runBaseTests(); diff --git a/source/java/org/alfresco/repo/search/impl/lucene/FilterIndexReaderByStringId.java b/source/java/org/alfresco/repo/search/impl/lucene/FilterIndexReaderByStringId.java index 098bb88ddb..118b648e4a 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/FilterIndexReaderByStringId.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/FilterIndexReaderByStringId.java @@ -85,6 +85,7 @@ public class FilterIndexReaderByStringId extends FilterIndexReader { deletedDocuments.set(td.doc()); } + td.close(); } } else @@ -108,8 +109,8 @@ public class FilterIndexReaderByStringId extends FilterIndexReader } } } - } + // searcher does not need to be closed, the reader is live } } catch (IOException e) @@ -244,20 +245,19 @@ public class FilterIndexReaderByStringId extends FilterIndexReader public boolean skipTo(int i) throws IOException { - boolean result = in.skipTo(i); - if (result == false) + if (!in.skipTo(i)) { return false; } - if (deletedDocuments.get(in.doc())) + while (deletedDocuments.get(in.doc())) { - return skipTo(i); - } - else - { - return true; + if (!in.next()) + { + return false; + } } + return true; } public void close() throws IOException diff --git a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfoTest.java b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfoTest.java index fb977b8295..1869724540 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfoTest.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfoTest.java @@ -127,6 +127,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -144,6 +145,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -164,6 +166,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -229,6 +232,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -246,6 +250,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -266,6 +271,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -304,6 +310,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -323,6 +330,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -346,6 +354,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -418,6 +427,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -435,6 +445,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -455,6 +466,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -503,6 +515,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } for (int j = 0; j < UPDATE_LIST.length; j++) { @@ -517,6 +530,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -536,6 +550,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } for (int j = 0; j < UPDATE_LIST.length; j++) { @@ -550,6 +565,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -573,6 +589,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } for (int j = 0; j < UPDATE_LIST.length; j++) { @@ -587,6 +604,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -687,6 +705,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -705,6 +724,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -726,6 +746,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -774,6 +795,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } for (int j = 0; j < update.length; j++) { @@ -788,6 +810,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -807,6 +830,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } for (int j = 0; j < update.length; j++) { @@ -821,6 +845,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); @@ -844,6 +869,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } for (int j = 0; j < update.length; j++) { @@ -858,6 +884,7 @@ public static final String[] UPDATE_LIST_2 = { "alpha2", "bravo2", "charlie2", " { assertFalse(tds.next()); } + tds.close(); } reader.close(); diff --git a/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCountingReadOnlyIndexReaderFactory.java b/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCountingReadOnlyIndexReaderFactory.java index efd4283b6f..101bf27611 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCountingReadOnlyIndexReaderFactory.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/index/ReferenceCountingReadOnlyIndexReaderFactory.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -32,10 +33,10 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; -import org.apache.lucene.document.FieldSelector; -import org.apache.lucene.document.FieldSelectorResult; import org.apache.lucene.document.Field.Index; import org.apache.lucene.document.Field.Store; +import org.apache.lucene.document.FieldSelector; +import org.apache.lucene.document.FieldSelectorResult; import org.apache.lucene.index.FilterIndexReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.Term; @@ -47,7 +48,7 @@ public class ReferenceCountingReadOnlyIndexReaderFactory { private static Log s_logger = LogFactory.getLog(ReferenceCountingReadOnlyIndexReaderFactory.class); - private static HashMap log = new HashMap(); + private static WeakHashMap log = new WeakHashMap(); public static IndexReader createReader(String id, IndexReader indexReader, boolean enableCaching, LuceneConfig config) { @@ -59,7 +60,7 @@ public class ReferenceCountingReadOnlyIndexReaderFactory s_logger.debug("Replacing ref counting reader for " + id); } s_logger.debug("Created ref counting reader for " + id + " " + rc.toString()); - log.put(id, rc); + log.put(new String(id), rc); // Copy the key because the RCROIR references the ID } return rc; } diff --git a/source/java/org/alfresco/repo/search/impl/parsers/CMISTest.java b/source/java/org/alfresco/repo/search/impl/parsers/CMISTest.java index 2486c97898..2af4fff2df 100644 --- a/source/java/org/alfresco/repo/search/impl/parsers/CMISTest.java +++ b/source/java/org/alfresco/repo/search/impl/parsers/CMISTest.java @@ -25,7 +25,6 @@ import junit.framework.TestCase; import org.alfresco.repo.node.BaseNodeServiceTest; import org.antlr.gunit.GrammarInfo; -import org.antlr.gunit.gUnitExecutor; import org.antlr.gunit.gUnitLexer; import org.antlr.gunit.gUnitParser; import org.antlr.runtime.ANTLRInputStream; diff --git a/source/java/org/alfresco/repo/search/impl/parsers/CMIS_FTSTest.java b/source/java/org/alfresco/repo/search/impl/parsers/CMIS_FTSTest.java index bf128d13f1..3e91d38690 100644 --- a/source/java/org/alfresco/repo/search/impl/parsers/CMIS_FTSTest.java +++ b/source/java/org/alfresco/repo/search/impl/parsers/CMIS_FTSTest.java @@ -26,7 +26,6 @@ import junit.framework.TestCase; import org.alfresco.repo.node.BaseNodeServiceTest; import org.antlr.gunit.GrammarInfo; -import org.antlr.gunit.gUnitExecutor; import org.antlr.gunit.gUnitLexer; import org.antlr.gunit.gUnitParser; import org.antlr.runtime.ANTLRInputStream; diff --git a/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryAfterInvocationProvider.java b/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryAfterInvocationProvider.java index f37b2f6d55..6bb1ba9f33 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryAfterInvocationProvider.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/acegi/ACLEntryAfterInvocationProvider.java @@ -733,8 +733,16 @@ public class ACLEntryAfterInvocationProvider implements AfterInvocationProvider, for (int i = 0; i < returnedObject.length(); i++) { long currentTimeMillis = System.currentTimeMillis(); - if (i >= maxChecks || (currentTimeMillis - startTimeMillis) > maxCheckTime) + if (i >= maxChecks) { + log.warn("maxChecks exceeded (" + maxChecks + ")", new Exception("Back Trace")); + filteringResultSet.setResultSetMetaData(new SimpleResultSetMetaData(LimitBy.NUMBER_OF_PERMISSION_EVALUATIONS, PermissionEvaluationMode.EAGER, returnedObject + .getResultSetMetaData().getSearchParameters())); + break; + } + else if ((currentTimeMillis - startTimeMillis) > maxCheckTime) + { + log.warn("maxCheckTime exceeded (" + (currentTimeMillis - startTimeMillis) + " milliseconds)", new Exception("Back Trace")); filteringResultSet.setResultSetMetaData(new SimpleResultSetMetaData(LimitBy.NUMBER_OF_PERMISSION_EVALUATIONS, PermissionEvaluationMode.EAGER, returnedObject .getResultSetMetaData().getSearchParameters())); break; diff --git a/source/java/org/alfresco/repo/site/SiteAspect.java b/source/java/org/alfresco/repo/site/SiteAspect.java index 6f3a25e62b..2fec100ae9 100644 --- a/source/java/org/alfresco/repo/site/SiteAspect.java +++ b/source/java/org/alfresco/repo/site/SiteAspect.java @@ -23,9 +23,11 @@ import org.alfresco.repo.node.NodeServicePolicies.OnMoveNodePolicy; import org.alfresco.repo.policy.Behaviour; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; /** * Site aspect behaviour bean. @@ -38,9 +40,20 @@ import org.alfresco.service.cmr.repository.NodeService; public class SiteAspect implements NodeServicePolicies.OnMoveNodePolicy { /** Services */ + private DictionaryService dictionaryService; private PolicyComponent policyComponent; private NodeService nodeService; + /** + * Set the dictionary service + * + * @param dictionaryService dictionary service + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + /** * Set the policy component * @@ -88,7 +101,8 @@ public class SiteAspect implements NodeServicePolicies.OnMoveNodePolicy // Deny renames if (oldParent.equals(newParent)) { - if (nodeService.getType((oldChildAssocRef.getChildRef())).equals(SiteModel.TYPE_SITE)) + QName type = nodeService.getType((oldChildAssocRef.getChildRef())); + if (dictionaryService.isSubClass(type, SiteModel.TYPE_SITE)) { throw new SiteServiceException("Sites can not be renamed."); } diff --git a/source/java/org/alfresco/repo/site/SiteServiceImpl.java b/source/java/org/alfresco/repo/site/SiteServiceImpl.java index f8f32aa080..01827e5e6b 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceImpl.java +++ b/source/java/org/alfresco/repo/site/SiteServiceImpl.java @@ -413,7 +413,8 @@ public class SiteServiceImpl implements SiteService, SiteModel // Create the site's groups String siteGroup = authorityService .createAuthority(AuthorityType.GROUP, getSiteGroup(shortName, false), shortName, shareZones); - Set permissions = permissionService.getSettablePermissions(SiteModel.TYPE_SITE); + QName siteType = nodeService.getType(siteNodeRef); + Set permissions = permissionService.getSettablePermissions(siteType); for (String permission : permissions) { // Create a group for the permission @@ -431,7 +432,8 @@ public class SiteServiceImpl implements SiteService, SiteModel // - give all authorities read permission on permissions so // memberships can be calculated // - add the current user to the site manager group - if (SiteVisibility.PUBLIC.equals(visibility) == true) + if (SiteVisibility.PUBLIC.equals(visibility) == true && + permissions.contains(SITE_CONSUMER)) { // From Alfresco 3.4 the 'site public' group is configurable. Out of the box it is // GROUP_EVERYONE so unconfigured behaviour is unchanged. But from 3.4 admins @@ -449,7 +451,8 @@ public class SiteServiceImpl implements SiteService, SiteModel permissionService.setPermission(siteNodeRef, sitePublicGroup, SITE_CONSUMER, true); } - else if (SiteVisibility.MODERATED.equals(visibility) == true) + else if (SiteVisibility.MODERATED.equals(visibility) == true && + permissions.contains(SITE_CONSUMER)) { // for moderated site EVERYONE has consumer access but site components do not. permissionService.setPermission(siteNodeRef, PermissionService.ALL_AUTHORITIES, SITE_CONSUMER, true); @@ -615,7 +618,11 @@ public class SiteServiceImpl implements SiteService, SiteModel } }, AuthenticationUtil.getSystemUserName()); - siteHomeRefs.put(tenantDomain, siteHomeRef); + // There may be domains with no sites (e.g. JSF-only clients). + if (siteHomeRef != null) + { + siteHomeRefs.put(tenantDomain, siteHomeRef); + } } return siteHomeRef; } @@ -1085,11 +1092,12 @@ public class SiteServiceImpl implements SiteService, SiteModel public void deleteSite(final String shortName) { logger.debug("delete site :" + shortName); - NodeRef siteNodeRef = getSiteNodeRef(shortName); + final NodeRef siteNodeRef = getSiteNodeRef(shortName); if (siteNodeRef == null) { throw new SiteServiceException(MSG_CAN_NOT_DELETE, new Object[]{shortName}); } + final QName siteType = nodeService.getType(siteNodeRef); // Delete the cached reference String cacheKey = this.tenantAdminService.getCurrentUserDomain() + '_' + shortName; @@ -1122,7 +1130,7 @@ public class SiteServiceImpl implements SiteService, SiteModel authorityService.deleteAuthority(getSiteGroup(shortName, true), false); // Iterate over the role related groups and delete then - Set permissions = permissionService.getSettablePermissions(SiteModel.TYPE_SITE); + Set permissions = permissionService.getSettablePermissions(siteType); for (String permission : permissions) { String siteRoleGroup = getSiteRoleGroup(shortName, permission, true); @@ -1190,8 +1198,9 @@ public class SiteServiceImpl implements SiteService, SiteModel } Map members = new HashMap(32); - - Set permissions = this.permissionService.getSettablePermissions(SiteModel.TYPE_SITE); + + QName siteType = nodeService.getType(siteNodeRef); + Set permissions = this.permissionService.getSettablePermissions(siteType); for (String permission : permissions) { if (roleFilter == null || roleFilter.length() == 0 || roleFilter.equals(permission)) @@ -1373,8 +1382,15 @@ public class SiteServiceImpl implements SiteService, SiteModel */ private List getPermissionGroups(String siteShortName, String authorityName) { + NodeRef siteNodeRef = getSiteNodeRef(siteShortName); + if (siteNodeRef == null) + { + throw new SiteServiceException(MSG_SITE_NO_EXIST, new Object[] { siteShortName }); + } + List fullResult = new ArrayList(5); - Set roles = this.permissionService.getSettablePermissions(SiteModel.TYPE_SITE); + QName siteType = nodeService.getType(siteNodeRef); + Set roles = this.permissionService.getSettablePermissions(siteType); // First use the authority's cached recursive group memberships to answer the question quickly Set authorityGroups = this.authorityService.getContainingAuthorities(AuthorityType.GROUP, @@ -1416,8 +1432,30 @@ public class SiteServiceImpl implements SiteService, SiteModel */ public List getSiteRoles() { - Set permissions = permissionService - .getSettablePermissions(SiteModel.TYPE_SITE); + return getSiteRoles(SiteModel.TYPE_SITE); + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#getSiteRoles(String) + */ + public List getSiteRoles(String shortName) + { + NodeRef siteNodeRef = getSiteNodeRef(shortName); + if (siteNodeRef == null) + { + throw new SiteServiceException(MSG_SITE_NO_EXIST, new Object[] { shortName }); + } + QName siteType = nodeService.getType(siteNodeRef); + return getSiteRoles(siteType); + } + + /** + * @see org.alfresco.service.cmr.site.SiteService#getSiteRoles() + * @see org.alfresco.service.cmr.site.SiteService#getSiteRoles(String) + */ + public List getSiteRoles(QName type) + { + Set permissions = permissionService.getSettablePermissions(type); return new ArrayList(permissions); } @@ -1709,7 +1747,14 @@ public class SiteServiceImpl implements SiteService, SiteModel */ private void setModeratedPermissions(String shortName, NodeRef containerNodeRef) { - Set permissions = permissionService.getSettablePermissions(SiteModel.TYPE_SITE); + NodeRef siteNodeRef = getSiteNodeRef(shortName); + if (siteNodeRef == null) + { + throw new SiteServiceException(MSG_SITE_NO_EXIST, new Object[] { shortName }); + } + + QName siteType = nodeService.getType(siteNodeRef); + Set permissions = permissionService.getSettablePermissions(siteType); for (String permission : permissions) { String permissionGroup = getSiteRoleGroup(shortName, permission, true); diff --git a/source/java/org/alfresco/repo/site/SiteServiceImplTest.java b/source/java/org/alfresco/repo/site/SiteServiceImplTest.java index b9ffe2a2db..356f947704 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceImplTest.java +++ b/source/java/org/alfresco/repo/site/SiteServiceImplTest.java @@ -18,8 +18,12 @@ */ package org.alfresco.repo.site; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + import java.io.Serializable; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -27,12 +31,18 @@ import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.model.ForumModel; +import org.alfresco.repo.dictionary.DictionaryDAO; +import org.alfresco.repo.dictionary.M2Model; +import org.alfresco.repo.dictionary.M2Property; +import org.alfresco.repo.dictionary.M2Type; import org.alfresco.repo.admin.SysAdminParams; import org.alfresco.repo.jscript.ClasspathScriptLocation; import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory; import org.alfresco.repo.node.archive.NodeArchiveService; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.TypeDefinition; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.repository.ContentWriter; @@ -50,6 +60,7 @@ import org.alfresco.service.cmr.site.SiteInfo; import org.alfresco.service.cmr.site.SiteService; import org.alfresco.service.cmr.site.SiteVisibility; import org.alfresco.service.cmr.tagging.TaggingService; +import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.BaseAlfrescoSpringTest; import org.alfresco.util.GUID; @@ -79,6 +90,8 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest private CopyService copyService; private ScriptService scriptService; private NodeService nodeService; + private NamespaceService namespaceService; + private DictionaryService dictionaryService; private AuthenticationComponent authenticationComponent; private TaggingService taggingService; private PersonService personService; @@ -118,6 +131,8 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest this.fileFolderService = (FileFolderService)this.applicationContext.getBean("FileFolderService"); this.nodeArchiveService = (NodeArchiveService)this.applicationContext.getBean("nodeArchiveService"); this.permissionService = (PermissionService)this.applicationContext.getBean("PermissionService"); + this.dictionaryService = (DictionaryService)this.applicationContext.getBean("DictionaryService"); + this.namespaceService = (NamespaceService)this.applicationContext.getBean("namespaceService"); this.siteService = (SiteService)this.applicationContext.getBean("SiteService"); // Big 'S' this.siteServiceImpl = (SiteServiceImpl) applicationContext.getBean("siteService"); // Small 's' this.sysAdminParams = (SysAdminParams)this.applicationContext.getBean("sysAdminParams"); @@ -984,11 +999,15 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest List roles = this.siteService.getSiteRoles(); assertNotNull(roles); assertFalse(roles.isEmpty()); + + // By default there are just the 4 roles + assertEquals(4, roles.size()); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONSUMER)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONTRIBUTOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_COLLABORATOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_MANAGER)); -// for (String role : roles) -// { -// System.out.println("Role: " + role); -// } + // For custom roles, see testCustomSiteType() } public void testCustomSiteProperties() @@ -1016,7 +1035,137 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest assertFalse(siteInfo.getCustomProperties().isEmpty()); assertEquals(1, siteInfo.getCustomProperties().size()); assertEquals("information", siteInfo.getCustomProperties().get(additionalInformationQName)); + } + + /** + * Creates a site with a custom type, and ensures that + * it behaves correctly. + */ + public void testCustomSiteType() + { + final String CS_URI = "http://example.com/site"; + final String CS_PFX = "cs"; + // Setup our custom site type + DictionaryDAO dictionaryDAO = (DictionaryDAO)this.applicationContext.getBean("dictionaryDAO"); + M2Model model = M2Model.createModel("cm:CustomSiteModel"); + model.createNamespace(CS_URI, CS_PFX); + + // Import the usual suspects too + model.createImport( + NamespaceService.CONTENT_MODEL_1_0_URI, + NamespaceService.CONTENT_MODEL_PREFIX + ); + model.createImport( + NamespaceService.DICTIONARY_MODEL_1_0_URI, + NamespaceService.DICTIONARY_MODEL_PREFIX + ); + model.createImport( + SiteModel.SITE_MODEL_URL, + SiteModel.SITE_MODEL_PREFIX + ); + + // Custom type + M2Type customType = model.createType("cs:customSite"); + customType.setTitle("customSite"); + customType.setParentName( + SiteModel.SITE_MODEL_PREFIX + ":" + + SiteModel.TYPE_SITE.getLocalName() + ); + + M2Property customProp = customType.createProperty("cs:customSiteProp"); + customProp.setTitle("customSiteProp"); + customProp.setType("d:text"); + dictionaryDAO.putModel(model); + + // Get our custom type, to check it's in there properly + final QName customTypeQ = QName.createQName("cs", "customSite", namespaceService); + TypeDefinition td = dictionaryService.getType(customTypeQ); + assertNotNull(td); + + // Create a site + SiteInfo site = siteService.createSite( + "custom", "custom", "Custom", "Custom", + SiteVisibility.PUBLIC + ); + + // Check the roles on it + List roles = siteService.getSiteRoles(); + assertEquals(4, roles.size()); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONSUMER)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONTRIBUTOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_COLLABORATOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_MANAGER)); + + roles = siteService.getSiteRoles(site.getShortName()); + assertEquals(4, roles.size()); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONSUMER)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONTRIBUTOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_COLLABORATOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_MANAGER)); + + + // Swap the type + nodeService.setType(site.getNodeRef(), customTypeQ); + + // Check again + roles = siteService.getSiteRoles(); + assertEquals(4, roles.size()); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONSUMER)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONTRIBUTOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_COLLABORATOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_MANAGER)); + + roles = siteService.getSiteRoles(site.getShortName()); + assertEquals(4, roles.size()); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONSUMER)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONTRIBUTOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_COLLABORATOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_MANAGER)); + + + // Alter the permissions for the custom site + PermissionService testPermissionService = spy( + (PermissionService)this.applicationContext.getBean("permissionServiceImpl") + ); + Set customPerms = new HashSet(); + customPerms.add(SiteServiceImpl.SITE_MANAGER); + customPerms.add("CUSTOM"); + when(testPermissionService.getSettablePermissions(customTypeQ)). + thenReturn(customPerms); + + // Check it changed for the custom site, but not normal + SiteServiceImpl siteServiceImpl = (SiteServiceImpl) + this.applicationContext.getBean("siteService"); + siteServiceImpl.setPermissionService(testPermissionService); + roles = siteService.getSiteRoles(); + + assertEquals(4, roles.size()); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONSUMER)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONTRIBUTOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_COLLABORATOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_MANAGER)); + + roles = siteService.getSiteRoles(site.getShortName()); + assertEquals(2, roles.size()); + assertEquals(true, roles.contains("CUSTOM")); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_MANAGER)); + + // Put the permissions back + siteServiceImpl.setPermissionService(permissionService); + roles = siteService.getSiteRoles(); + assertEquals(4, roles.size()); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONSUMER)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONTRIBUTOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_COLLABORATOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_MANAGER)); + + roles = siteService.getSiteRoles(site.getShortName()); + assertEquals(4, roles.size()); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONSUMER)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_CONTRIBUTOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_COLLABORATOR)); + assertEquals(true, roles.contains(SiteServiceImpl.SITE_MANAGER)); } public void testGroupMembership() diff --git a/source/java/org/alfresco/repo/site/script/ScriptSiteService.java b/source/java/org/alfresco/repo/site/script/ScriptSiteService.java index 445c48d961..4f30547b0c 100644 --- a/source/java/org/alfresco/repo/site/script/ScriptSiteService.java +++ b/source/java/org/alfresco/repo/site/script/ScriptSiteService.java @@ -217,11 +217,9 @@ public class ScriptSiteService extends BaseScopableProcessorExtension } /** - * This method cleans up the permissions on the specified node and all its primary children. * It removes permissions which pertain to sites other than the node's current site. * * @param targetNode the root node which is to have its permissions cleaned. - * @since 3.4.2 * @see SiteService#cleanSitePermissions(NodeRef, SiteInfo) */ public void cleanSitePermissions(NodeRef targetNode) @@ -233,7 +231,6 @@ public class ScriptSiteService extends BaseScopableProcessorExtension * This method cleans up the permissions on the specified node and all its primary children. * It removes permissions which pertain to sites other than the node's current site. * - * @param targetNode the root node which is to have its permissions cleaned. * @since 3.4.2 * @see SiteService#cleanSitePermissions(NodeRef, SiteInfo) */ @@ -241,4 +238,16 @@ public class ScriptSiteService extends BaseScopableProcessorExtension { this.cleanSitePermissions(targetNode.getNodeRef()); } + + /** + * Returns an array of all the roles that can be assigned to a member of a + * specific site. + * + * @return String[] roles available to assign to a member of a site + */ + public String[] listSiteRoles(String shortName) + { + List roles = this.siteService.getSiteRoles(shortName); + return (String[])roles.toArray(new String[roles.size()]); + } } diff --git a/source/java/org/alfresco/repo/site/script/Site.java b/source/java/org/alfresco/repo/site/script/Site.java index e708422319..73fd6be9a7 100644 --- a/source/java/org/alfresco/repo/site/script/Site.java +++ b/source/java/org/alfresco/repo/site/script/Site.java @@ -255,7 +255,9 @@ public class Site implements Serializable { if (this.siteRoleGroups == null) { - List roles = this.siteService.getSiteRoles(); + List roles = this.siteService.getSiteRoles( + this.siteInfo.getShortName() + ); this.siteRoleGroups = new ScriptableHashMap(); for (String role : roles) { diff --git a/source/java/org/alfresco/repo/tagging/TaggingServiceImplTest.java b/source/java/org/alfresco/repo/tagging/TaggingServiceImplTest.java index b339f0c3cc..5c1c641116 100644 --- a/source/java/org/alfresco/repo/tagging/TaggingServiceImplTest.java +++ b/source/java/org/alfresco/repo/tagging/TaggingServiceImplTest.java @@ -43,6 +43,7 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransacti import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.action.ActionTrackingService; +import org.alfresco.service.cmr.action.ExecutionSummary; import org.alfresco.service.cmr.audit.AuditService; import org.alfresco.service.cmr.coci.CheckOutCheckInService; import org.alfresco.service.cmr.repository.CopyService; @@ -63,6 +64,8 @@ import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.GUID; import org.alfresco.util.PropertyMap; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.context.ConfigurableApplicationContext; /** @@ -76,6 +79,8 @@ public class TaggingServiceImplTest extends TestCase private static ConfigurableApplicationContext ctx = (ConfigurableApplicationContext)ApplicationContextHelper.getApplicationContext(); + private static final Log logger = LogFactory.getLog(TaggingServiceImplTest.class); + /** Services */ private TaggingService taggingService; private NodeService nodeService; @@ -181,6 +186,11 @@ public class TaggingServiceImplTest extends TestCase new JavaBehaviour(asyncOccurs, "onAsyncActionExecute", NotificationFrequency.EVERY_EVENT) ); + // We do want action tracking whenever the tag scope updater runs + UpdateTagScopesActionExecuter updateTagsAction = + (UpdateTagScopesActionExecuter)ctx.getBean("update-tagscope"); + updateTagsAction.setTrackStatus(true); + // Create the folders and documents to be tagged createTestDocumentsAndFolders(); } @@ -1710,6 +1720,12 @@ public class TaggingServiceImplTest extends TestCase assertTrue("Not found in " + pendingScopes, pendingScopes.contains(subFolder)); + // Ensure that we've still got the lock, eg in case + // of the async execution taking a while to proceed + updateTagsAction.updateTagScopeLock(folder, testData.lockF); + updateTagsAction.updateTagScopeLock(subFolder, testData.lockSF); + + // Have the Quartz bean fire now // It won't be able to do anything, as the locks are taken UpdateTagScopesQuartzJob job = new UpdateTagScopesQuartzJob(); @@ -1775,7 +1791,7 @@ public class TaggingServiceImplTest extends TestCase * Test that when multiple threads do tag updates, the right thing still * happens */ - public void DISABLEDtestMultiThreaded() throws Exception + public void testMultiThreaded() throws Exception { transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { @@ -1791,6 +1807,7 @@ public class TaggingServiceImplTest extends TestCase // Reset the action count asyncOccurs.wantedActionsCount = 0; + // Prepare a bunch of threads to do tagging final List threads = new ArrayList(); final String[] tags = new String[] { TAG_1, TAG_2, TAG_3, TAG_4, TAG_5, @@ -1813,7 +1830,7 @@ public class TaggingServiceImplTest extends TestCase catch (InterruptedException e) { } - System.out.println(Thread.currentThread() + " - About to start tagging for " + tag); + logger.debug(Thread.currentThread() + " - About to start tagging for " + tag); // Do the updates AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName()); @@ -1825,12 +1842,12 @@ public class TaggingServiceImplTest extends TestCase taggingService.addTag(folder, tag); taggingService.addTag(subFolder, tag); taggingService.addTag(subDocument, tag); - System.out.println(Thread.currentThread() + " - Tagging for " + tag); + logger.debug(Thread.currentThread() + " - Tagging for " + tag); return null; } }, false, true ); - System.out.println(Thread.currentThread() + " - Done tagging for " + tag); + logger.debug(Thread.currentThread() + " - Done tagging for " + tag); // Wait briefly for thing to catch up, before we // declare ourselves to be done @@ -1844,7 +1861,7 @@ public class TaggingServiceImplTest extends TestCase } // Release the threads - System.out.println("Releasing tagging threads"); + logger.info("Releasing tagging threads"); for (Thread t : threads) { t.interrupt(); @@ -1856,7 +1873,7 @@ public class TaggingServiceImplTest extends TestCase { t.join(); } - System.out.println("All threads should have finished"); + logger.info("All threads should have finished"); // Have a brief pause, while we wait for their related // async actions to kick off @@ -1873,11 +1890,20 @@ public class TaggingServiceImplTest extends TestCase { if(asyncOccurs.wantedActionsCount < tags.length) { + if(i%50 == 0) + { + logger.info("Done " + asyncOccurs.wantedActionsCount + " of " + tags.length); + } Thread.sleep(100); continue; } if (actionTrackingService.getAllExecutingActions().size() > 0) { + if(i%50 == 0) + { + List actions = actionTrackingService.getAllExecutingActions(); + logger.info("Waiting on " + actions.size() + " actions: " + actions); + } Thread.sleep(100); continue; } diff --git a/source/java/org/alfresco/repo/tagging/UpdateTagScopesActionExecuter.java b/source/java/org/alfresco/repo/tagging/UpdateTagScopesActionExecuter.java index bc358ff26f..cdf165f28a 100644 --- a/source/java/org/alfresco/repo/tagging/UpdateTagScopesActionExecuter.java +++ b/source/java/org/alfresco/repo/tagging/UpdateTagScopesActionExecuter.java @@ -93,6 +93,9 @@ public class UpdateTagScopesActionExecuter extends ActionExecuterAbstractBase /** What's the largest number of updates we should claim for a tag scope in one transaction? */ private static final int tagUpdateBatchSize = 100; + + /** How long to lock a tag scope for */ + private static final int tagScopeLockTime = 2500; // For searching private static final String noderefPath = @@ -540,10 +543,14 @@ public class UpdateTagScopesActionExecuter extends ActionExecuterAbstractBase protected String lockTagScope(NodeRef tagScope) { String lock = jobLockService.getLock( - tagScopeToLockQName(tagScope), 2500, 0, 0 + tagScopeToLockQName(tagScope), tagScopeLockTime, 0, 0 ); return lock; } + protected void updateTagScopeLock(NodeRef tagScope, String lockToken) + { + jobLockService.refreshLock(lockToken, tagScopeToLockQName(tagScope), tagScopeLockTime); + } protected void unlockTagScope(NodeRef tagScope, String lockToken) { jobLockService.releaseLock(lockToken, tagScopeToLockQName(tagScope)); diff --git a/source/java/org/alfresco/repo/template/AVMTemplateNode.java b/source/java/org/alfresco/repo/template/AVMTemplateNode.java index 0d99e4b7ad..c7a1ecf1d0 100644 --- a/source/java/org/alfresco/repo/template/AVMTemplateNode.java +++ b/source/java/org/alfresco/repo/template/AVMTemplateNode.java @@ -35,6 +35,7 @@ import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.avm.AVMNodeDescriptor; import org.alfresco.service.cmr.avm.locking.AVMLockingService.LockState; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentReader; @@ -371,7 +372,13 @@ public class AVMTemplateNode extends BasePermissionsNode implements NamespacePre Map props = this.services.getAVMService().getNodeProperties(this.version, this.path); for (QName qname: props.keySet()) { - Serializable propValue = props.get(qname).getValue(DataTypeDefinition.ANY); + PropertyDefinition propertyDefinition = services.getDictionaryService().getProperty(qname); + QName currentPropertyType = DataTypeDefinition.ANY; + if (null != propertyDefinition) + { + currentPropertyType = propertyDefinition.getDataType().getName(); + } + Serializable propValue = props.get(qname).getValue(currentPropertyType); if (propValue instanceof NodeRef) { // NodeRef object properties are converted to new TemplateNode objects diff --git a/source/java/org/alfresco/repo/template/AVMTemplateNodeTest.java b/source/java/org/alfresco/repo/template/AVMTemplateNodeTest.java new file mode 100644 index 0000000000..97b7bf8777 --- /dev/null +++ b/source/java/org/alfresco/repo/template/AVMTemplateNodeTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.template; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import junit.framework.TestCase; + +import org.alfresco.repo.avm.AVMNodeConverter; +import org.alfresco.repo.domain.PropertyValue; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.avm.AVMService; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ApplicationContextHelper; +import org.springframework.context.ApplicationContext; + +/** + * @author Dmitry Velichkevich + */ +public class AVMTemplateNodeTest extends TestCase +{ + private static final String TEST_WCM_NAMESPACE = "http://www.alfresco.org/model/testwcmmodel/1.0"; + + private static final QName ASPECT_AUTHORED = QName.createQName(TEST_WCM_NAMESPACE, "authored"); + private static final QName PROP_AUTHORED_DATE = QName.createQName(TEST_WCM_NAMESPACE, "dateAuthored"); + + private static final ApplicationContext APPLICATION_CONTEXT = ApplicationContextHelper.getApplicationContext(new String[] { "classpath:alfresco/application-context.xml", + "classpath:test/alfresco/wcm-template-node-test-context.xml" }); + private static final ServiceRegistry SERVICE_REGISTRY = (ServiceRegistry) APPLICATION_CONTEXT.getBean(ServiceRegistry.SERVICE_REGISTRY); + + private AVMService avmService = SERVICE_REGISTRY.getAVMService(); + + @Override + protected void setUp() throws Exception + { + avmService.createStore("main"); + avmService.createDirectory("main:/", "root"); + avmService.createFile("main:/root", "testfile.txt"); + } + + @Override + protected void tearDown() throws Exception + { + avmService.purgeStore("main"); + } + + @SuppressWarnings("unchecked") + public void testDatePropertiesConversion() throws Exception + { + assertNotNull("Aspect 'twcm:authored' is not in the set of compiled dictionary models", SERVICE_REGISTRY.getDictionaryService().getAspect(ASPECT_AUTHORED)); + + List values = new LinkedList(); + for (int i = 0; i < 5; i++) + { + values.add(new Date()); + } + PropertyValue value = new PropertyValue(DataTypeDefinition.TEXT, (Serializable) values); + + avmService.addAspect("main:/root/testfile.txt", ASPECT_AUTHORED); + avmService.setNodeProperty("main:/root/testfile.txt", PROP_AUTHORED_DATE, value); + NodeRef nodeRef = AVMNodeConverter.ToNodeRef(-1, "main:/root/testfile.txt"); + AVMTemplateNode templateNode = new AVMTemplateNode(nodeRef, SERVICE_REGISTRY, null); + + Map properties = templateNode.getProperties(); + assertNotNull(properties); + assertFalse(properties.isEmpty()); + assertTrue(properties.containsKey(PROP_AUTHORED_DATE)); + + Collection authoredDates = (Collection) properties.get(PROP_AUTHORED_DATE); + assertNotNull(authoredDates); + assertFalse(authoredDates.isEmpty()); + + for (Serializable date : authoredDates) + { + assertFalse(("Unexpected data type of 'twcm:authored' property values: " + ((null != date) ? (date.getClass().getName()) : ("null"))), date instanceof String); + } + } +} diff --git a/source/java/org/alfresco/repo/transfer/TransferServiceImpl2.java b/source/java/org/alfresco/repo/transfer/TransferServiceImpl2.java index 7b469f9fe8..58785fa3f5 100644 --- a/source/java/org/alfresco/repo/transfer/TransferServiceImpl2.java +++ b/source/java/org/alfresco/repo/transfer/TransferServiceImpl2.java @@ -237,14 +237,7 @@ public class TransferServiceImpl2 implements TransferService2 // enableable aspect properties.put(TransferModel.PROP_ENABLED, Boolean.TRUE); - NodeRef home = getTransferHome(); - - /** - * Work out which group the transfer target is for, in this case the - * default group. - */ - NodeRef defaultGroup = nodeService.getChildByName(home, ContentModel.ASSOC_CONTAINS, - defaultTransferGroup); + NodeRef defaultGroup = getDefaultGroup(); /** * Go ahead and create the new node @@ -262,6 +255,14 @@ public class TransferServiceImpl2 implements TransferService2 return retVal; } + private NodeRef getDefaultGroup() + { + NodeRef home = getTransferHome(); + NodeRef defaultGroup = nodeService.getChildByName(home, ContentModel.ASSOC_CONTAINS, + defaultTransferGroup); + return defaultGroup; + } + /** * Get all transfer targets */ @@ -1184,18 +1185,28 @@ public class TransferServiceImpl2 implements TransferService2 { String query = transferSpaceQuery; - ResultSet result = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, - SearchService.LANGUAGE_XPATH, query); - - if(result.length() == 0) + ResultSet result = null; + try { - // No transfer home. - throw new TransferException(MSG_NO_HOME, new Object[]{query}); + result = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, query); + + if (result.length() == 0) + { + // No transfer home. + throw new TransferException(MSG_NO_HOME, new Object[] { query }); + } + if (result.getNodeRefs().size() != 0) + { + transferHome = result.getNodeRef(0); + transferHomeMap.put(tenantDomain, transferHome); + } } - if (result.getNodeRefs().size() != 0) + finally { - transferHome = result.getNodeRef(0); - transferHomeMap.put(tenantDomain, transferHome); + if (result != null) + { + result.close(); + } } } return transferHome; @@ -1225,16 +1236,8 @@ public class TransferServiceImpl2 implements TransferService2 */ private NodeRef lookupTransferTarget(String name) { - String query = "+TYPE:\"trx:transferTarget\" +@cm\\:name:\"" +name + "\""; - - ResultSet result = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, - SearchService.LANGUAGE_LUCENE, query); - - if(result.length() == 1) - { - return result.getNodeRef(0); - } - return null; + NodeRef defaultGroup = getDefaultGroup(); + return nodeService.getChildByName(defaultGroup, ContentModel.ASSOC_CONTAINS, name); } private void mapTransferTarget(NodeRef nodeRef, TransferTargetImpl def) diff --git a/source/java/org/alfresco/repo/transfer/TransferServiceImplTest.java b/source/java/org/alfresco/repo/transfer/TransferServiceImplTest.java index 8f941ea154..7eac2e4bea 100644 --- a/source/java/org/alfresco/repo/transfer/TransferServiceImplTest.java +++ b/source/java/org/alfresco/repo/transfer/TransferServiceImplTest.java @@ -342,6 +342,52 @@ public class TransferServiceImplTest extends BaseAlfrescoSpringTest } } + /** + * Test that if someone copies a transfer group using a client app then the getTransferTarget operations still succeed + * + * @throws Exception + */ + public void testALF6565() throws Exception + { + String nameA = GUID.generate(); + String nameB = GUID.generate(); + String title = "title"; + String description = "description"; + String endpointProtocol = "http"; + String endpointHost = "localhost"; + int endpointPort = 8080; + String endpointPath = "rhubarb"; + String username = "admin"; + char[] password = "password".toCharArray(); + + /** + * Now go ahead and create our first transfer target + */ + TransferTarget targetA = transferService.createAndSaveTransferTarget(nameA, title, description, endpointProtocol, endpointHost, endpointPort, endpointPath, username, password); + TransferTarget targetB = transferService.createAndSaveTransferTarget(nameB, title, description, endpointProtocol, endpointHost, endpointPort, endpointPath, username, password); + + NodeRef transferHome = transferServiceImpl.getTransferHome(); + NodeRef defaultGroup = nodeService.getChildByName(transferHome, ContentModel.ASSOC_CONTAINS, + transferServiceImpl.getDefaultTransferGroup()); + assertNotNull(defaultGroup); + copyService.copyAndRename(defaultGroup, transferHome, ContentModel.ASSOC_CONTAINS, QName.createQName("test"), true); + + Set targets = transferService.getTransferTargets(); + + int targetACount = 0; + int targetBCount = 0; + for (TransferTarget target : targets) + { + if (target.getName().equals(nameA)) ++targetACount; + if (target.getName().equals(nameB)) ++targetBCount; + } + assertEquals(2, targetACount); + assertEquals(2, targetBCount); + + assertEquals(targetA.getNodeRef(), transferService.getTransferTarget(nameA).getNodeRef()); + assertEquals(targetB.getNodeRef(), transferService.getTransferTarget(nameB).getNodeRef()); + } + /** * Test of Get All Transfer Targets By Group */ diff --git a/source/java/org/alfresco/repo/usage/ContentUsageImpl.java b/source/java/org/alfresco/repo/usage/ContentUsageImpl.java index 2d3aa85446..017e7b2fc7 100644 --- a/source/java/org/alfresco/repo/usage/ContentUsageImpl.java +++ b/source/java/org/alfresco/repo/usage/ContentUsageImpl.java @@ -502,7 +502,7 @@ public class ContentUsageImpl implements ContentUsageService, } } - private long getUserStoredUsage(NodeRef personNodeRef) + public long getUserStoredUsage(NodeRef personNodeRef) { Long currentUsage = null; if (personNodeRef != null) @@ -516,10 +516,10 @@ public class ContentUsageImpl implements ContentUsageService, public long getUserUsage(String userName) { ParameterCheck.mandatoryString("userName", userName); - return getUserUsage(getPerson(userName)); + return getUserUsage(getPerson(userName), false); } - public long getUserUsage(NodeRef personNodeRef) + public long getUserUsage(NodeRef personNodeRef, boolean removeDeltas) { long currentUsage = -1; @@ -530,8 +530,10 @@ public class ContentUsageImpl implements ContentUsageService, if (currentUsage != -1) { - // add any deltas - currentUsage = currentUsage + usageService.getTotalDeltaSize(personNodeRef); + long deltaSize = removeDeltas ? usageService.getAndRemoveTotalDeltaSize(personNodeRef) : + usageService.getTotalDeltaSize(personNodeRef); + // add any deltas to the currentUsage, removing them if required + currentUsage = currentUsage + deltaSize; if (currentUsage < 0) { diff --git a/source/java/org/alfresco/repo/usage/UsageServiceImpl.java b/source/java/org/alfresco/repo/usage/UsageServiceImpl.java index 17cd2ce141..16037855e2 100644 --- a/source/java/org/alfresco/repo/usage/UsageServiceImpl.java +++ b/source/java/org/alfresco/repo/usage/UsageServiceImpl.java @@ -53,7 +53,7 @@ public class UsageServiceImpl implements UsageService { return usageDAO.getTotalDeltaSize(usageNodeRef, true); } - + public Set getUsageDeltaNodes() { return usageDAO.getUsageDeltaNodes(); diff --git a/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java b/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java index 7a013387b0..90b0c67a22 100644 --- a/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java +++ b/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java @@ -556,23 +556,28 @@ public class UserUsageTrackingComponent extends AbstractLifecycleBean if (nodeType.equals(ContentModel.TYPE_PERSON)) { NodeRef personNodeRef = usageNodeRef; - // collapse the usage deltas - long currentUsage = contentUsageImpl.getUserUsage(personNodeRef); + String userName = (String)nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); + + long currentUsage = contentUsageImpl.getUserStoredUsage(personNodeRef); if (currentUsage != -1) { - usageService.deleteDeltas(personNodeRef); + // Collapse the usage deltas + // Calculate and remove deltas in one go to guard against deletion of + // deltas from another transaction that have not been included in the + // calculation + currentUsage = contentUsageImpl.getUserUsage(personNodeRef, true); contentUsageImpl.setUserStoredUsage(personNodeRef, currentUsage); if (logger.isTraceEnabled()) { - logger.trace("Collapsed usage: personNodeRef=" + personNodeRef + ", usage=" + currentUsage); + logger.trace("Collapsed usage: username=" + userName + ", usage=" + currentUsage); } } else { if (logger.isWarnEnabled()) { - logger.warn("Initial usage has not yet been calculated: personNodeRef=" + personNodeRef); + logger.warn("Initial usage for user has not yet been calculated: " + userName); } } } diff --git a/source/java/org/alfresco/repo/version/NodeServiceImplTest.java b/source/java/org/alfresco/repo/version/NodeServiceImplTest.java index aafcceac20..8bdf34d367 100644 --- a/source/java/org/alfresco/repo/version/NodeServiceImplTest.java +++ b/source/java/org/alfresco/repo/version/NodeServiceImplTest.java @@ -132,7 +132,7 @@ public class NodeServiceImplTest extends BaseVersionStoreTest //assertEquals(origProps.size(), versionedProperties.size()); // check version label - assertEquals("1.0", versionedProperties.get(ContentModel.PROP_VERSION_LABEL)); + assertEquals("first version label", "0.1", versionedProperties.get(ContentModel.PROP_VERSION_LABEL)); // TODO do futher versioning and check by changing values } @@ -622,8 +622,8 @@ public class NodeServiceImplTest extends BaseVersionStoreTest // Now French - String frProp = "C'est une propri\u00e9t\u00e9 en fran\u00e7ais"; // C'est une propriété en français - QName frQName = QName.createQName("NameSpace", "En Fran\u00e7ais"); // En Français + String frProp = "C'est une propri\u00e9t\u00e9 en fran\u00e7ais"; // C'est une propriété en français + QName frQName = QName.createQName("NameSpace", "En Fran\u00e7ais"); // En Français NodeRef frNode = nodeService.createNode( this.rootNodeRef, ContentModel.ASSOC_CONTAINS, frQName, ContentModel.TYPE_CONTENT @@ -645,8 +645,8 @@ public class NodeServiceImplTest extends BaseVersionStoreTest // Next Spanish - String esProp = "Esta es una propiedad en Espa\u00f1ol"; // Esta es una propiedad en Español - QName esQName = QName.createQName("NameSpace", "En Espa\u00f1ol"); // En Español + String esProp = "Esta es una propiedad en Espa\u00f1ol"; // Esta es una propiedad en Español + QName esQName = QName.createQName("NameSpace", "En Espa\u00f1ol"); // En Español NodeRef esNode = nodeService.createNode( this.rootNodeRef, ContentModel.ASSOC_CONTAINS, esQName, ContentModel.TYPE_CONTENT @@ -666,8 +666,8 @@ public class NodeServiceImplTest extends BaseVersionStoreTest // Finally Japanese - String jpProp = "\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u304f\u3060\u3055\u3044\u3002"; // をクリックしてください。 - QName jpQName = QName.createQName("NameSpace", "\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u304f"); // をクリックしてく + String jpProp = "\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u304f\u3060\u3055\u3044\u3002"; // をクリックã�—ã�¦ã��ã� ã�•ã�„。 + QName jpQName = QName.createQName("NameSpace", "\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u304f"); // をクリックã�—ã�¦ã�� NodeRef jpNode = nodeService.createNode( this.rootNodeRef, ContentModel.ASSOC_CONTAINS, jpQName, ContentModel.TYPE_CONTENT diff --git a/source/java/org/alfresco/repo/version/VersionServiceImplTest.java b/source/java/org/alfresco/repo/version/VersionServiceImplTest.java index 2df349ae2f..bddcc27a50 100644 --- a/source/java/org/alfresco/repo/version/VersionServiceImplTest.java +++ b/source/java/org/alfresco/repo/version/VersionServiceImplTest.java @@ -52,6 +52,7 @@ import org.alfresco.service.cmr.version.VersionHistory; import org.alfresco.service.cmr.version.VersionService; import org.alfresco.service.cmr.version.VersionServiceException; import org.alfresco.service.cmr.version.VersionType; +import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.GUID; @@ -93,6 +94,7 @@ public class VersionServiceImplTest extends BaseVersionStoreTest { super.onTearDownAfterTransaction(); versionableAspect.setExcludedOnUpdateProps(excludedOnUpdateProps); + versionableAspect.afterDictionaryInit(); } public void testSetup() @@ -585,13 +587,13 @@ public class VersionServiceImplTest extends BaseVersionStoreTest // Check that the version label is correct on the versionable node String versionLabel1 = (String)this.dbNodeService.getProperty(versionableNode, ContentModel.PROP_VERSION_LABEL); - assertEquals("1.1", versionLabel1); + assertEquals("first version label", "0.2", versionLabel1); assertEquals(version1.getVersionLabel(), versionLabel1); // Check the version history List expectedVersions = new ArrayList(2); - expectedVersions.add(version1); - expectedVersions.add(version0); + expectedVersions.add(version1); // 0.1 + expectedVersions.add(version0); // 0.2 versionHistory = this.versionService.getVersionHistory(versionableNode); assertEquals(2, versionHistory.getAllVersions().size()); CheckVersionHistory(versionHistory, expectedVersions); @@ -602,12 +604,12 @@ public class VersionServiceImplTest extends BaseVersionStoreTest assertEquals(currentVersion.getFrozenStateNodeRef(), version1.getFrozenStateNodeRef()); // Create a couple more versions - Version version2 = createVersion(versionableNode); - Version version3 = createVersion(versionableNode); + Version version2 = createVersion(versionableNode); // 0.3 + Version version3 = createVersion(versionableNode); //0.4 // Check that the version label is correct on the versionable node String versionLabel3 = (String)this.dbNodeService.getProperty(versionableNode, ContentModel.PROP_VERSION_LABEL); - assertEquals("1.3", versionLabel3); + assertEquals("0.4", versionLabel3); assertEquals(version3.getVersionLabel(), versionLabel3); // Check the version history @@ -1069,8 +1071,10 @@ public class VersionServiceImplTest extends BaseVersionStoreTest }); List excludedOnUpdateProps = new ArrayList(1); - excludedOnUpdateProps.add(ContentModel.PROP_AUTHOR.toPrefixString()); + NamespaceService namespaceService = (NamespaceService) applicationContext.getBean("namespaceService"); + excludedOnUpdateProps.add(ContentModel.PROP_AUTHOR.toPrefixString(namespaceService)); versionableAspect.setExcludedOnUpdateProps(excludedOnUpdateProps); + versionableAspect.afterDictionaryInit(); // test auto-version props on - with an excluded prop change diff --git a/source/java/org/alfresco/repo/version/VersionableAspect.java b/source/java/org/alfresco/repo/version/VersionableAspect.java index bdcb5ea092..515dc0aa45 100644 --- a/source/java/org/alfresco/repo/version/VersionableAspect.java +++ b/source/java/org/alfresco/repo/version/VersionableAspect.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -21,8 +21,10 @@ package org.alfresco.repo.version; import java.io.Serializable; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.ContentServicePolicies; @@ -30,6 +32,8 @@ import org.alfresco.repo.copy.CopyBehaviourCallback; import org.alfresco.repo.copy.CopyDetails; import org.alfresco.repo.copy.CopyServicePolicies; import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; +import org.alfresco.repo.dictionary.DictionaryDAO; +import org.alfresco.repo.dictionary.DictionaryListener; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.Behaviour; import org.alfresco.repo.policy.JavaBehaviour; @@ -41,6 +45,7 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.version.Version; import org.alfresco.service.cmr.version.VersionService; import org.alfresco.service.cmr.version.VersionType; +import org.alfresco.service.namespace.NamespacePrefixResolver; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.EqualsHelper; @@ -57,7 +62,8 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate NodeServicePolicies.OnDeleteNodePolicy, NodeServicePolicies.OnUpdatePropertiesPolicy, VersionServicePolicies.AfterCreateVersionPolicy, - CopyServicePolicies.OnCopyNodePolicy + CopyServicePolicies.OnCopyNodePolicy, + DictionaryListener { /** The i18n'ized messages */ private static final String MSG_INITIAL_VERSION = "create_version.initial_version"; @@ -75,6 +81,12 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate /** The Version service */ private VersionService versionService; + + /** The dictionary DAO. */ + private DictionaryDAO dictionaryDAO; + + /** The Namespace Prefix Resolver. */ + private NamespacePrefixResolver namespacePrefixResolver; /** Behaviours */ JavaBehaviour onUpdatePropertiesBehaviour; @@ -86,6 +98,8 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate */ private List excludedOnUpdateProps = Collections.emptyList(); + private Set excludedOnUpdatePropQNames = Collections.emptySet(); + /** * Set the policy component * @@ -115,8 +129,30 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate { this.nodeService = nodeService; } + + /** + * Sets the dictionary DAO. + * + * @param dictionaryDAO + * the dictionary DAO + */ + public void setDictionaryDAO(DictionaryDAO dictionaryDAO) + { + this.dictionaryDAO = dictionaryDAO; + } /** + * Sets the namespace prefix resolver. + * + * @param namespacePrefixResolver + * the namespace prefix resolver + */ + public void setNamespacePrefixResolver(NamespacePrefixResolver namespacePrefixResolver) + { + this.namespacePrefixResolver = namespacePrefixResolver; + } + + /** * @return Returns the current list of properties that do not trigger versioning */ public List getExcludedOnUpdateProps() @@ -172,6 +208,8 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), ContentModel.ASPECT_VERSIONABLE, new JavaBehaviour(this, "getCopyCallback")); + + this.dictionaryDAO.register(this); } /** @@ -364,27 +402,16 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate if ((autoVersion == true) && (autoVersionProps == true)) { // Check for explicitly excluded props - if one or more excluded props changes then do not auto-version on this event (even if other props changed) - if (excludedOnUpdateProps.size() > 0) + if (excludedOnUpdatePropQNames.size() > 0) { - Map propNames = new HashMap(after.size()); - for (QName afterProp : after.keySet()) - { - if (excludedOnUpdateProps.contains(afterProp.getPrefixString())) - { - propNames.put(afterProp.getPrefixString(), afterProp); - } - } - for (QName beforeProp : before.keySet()) - { - if (excludedOnUpdateProps.contains(beforeProp.getPrefixString())) - { - propNames.put(beforeProp.getPrefixString(), beforeProp); - } - } - + Set propNames = new HashSet(after.size() * 2); + propNames.addAll(after.keySet()); + propNames.addAll(before.keySet()); + propNames.retainAll(excludedOnUpdatePropQNames); + if (propNames.size() > 0) { - for (QName prop : propNames.values()) + for (QName prop : propNames) { Serializable beforeValue = before.get(prop); Serializable afterValue = after.get(prop); @@ -447,4 +474,43 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate } versionedNodeRefs.put(versionableNode, versionableNode); } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.dictionary.DictionaryListener#onDictionaryInit() + */ + @Override + public void onDictionaryInit() + { + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.dictionary.DictionaryListener#afterDictionaryInit() + */ + @Override + public void afterDictionaryInit() + { + this.excludedOnUpdatePropQNames = new HashSet(this.excludedOnUpdateProps.size() * 2); + for (String prefixString : this.excludedOnUpdateProps) + { + try + { + this.excludedOnUpdatePropQNames.add(QName.createQName(prefixString, this.namespacePrefixResolver)); + } + catch (Exception e) + { + // An unregistered prefix. Ignore and continue + } + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.dictionary.DictionaryListener#afterDictionaryDestroy() + */ + @Override + public void afterDictionaryDestroy() + { + } } diff --git a/source/java/org/alfresco/repo/version/common/versionlabel/SerialVersionLabelPolicy.java b/source/java/org/alfresco/repo/version/common/versionlabel/SerialVersionLabelPolicy.java index beceeaeeb4..1b2dd26ebc 100644 --- a/source/java/org/alfresco/repo/version/common/versionlabel/SerialVersionLabelPolicy.java +++ b/source/java/org/alfresco/repo/version/common/versionlabel/SerialVersionLabelPolicy.java @@ -69,30 +69,32 @@ public class SerialVersionLabelPolicy implements CalculateVersionLabelPolicy { SerialVersionLabel serialVersionNumber = null; + VersionType versionType = null; + if (versionProperties != null) + { + versionType = (VersionType)versionProperties.get(VersionModel.PROP_VERSION_TYPE); + } + if (preceedingVersion != null) { + // There is a preceeding version serialVersionNumber = new SerialVersionLabel(preceedingVersion.getVersionLabel()); - - VersionType versionType = null; - if (versionProperties != null) - { - versionType = (VersionType)versionProperties.get(VersionModel.PROP_VERSION_TYPE); - } - - if (VersionType.MAJOR.equals(versionType) == true) - { - serialVersionNumber.majorIncrement(); - } - else - { - serialVersionNumber.minorIncrement(); - } } else { + // This is the first version serialVersionNumber = new SerialVersionLabel(null); } + if (VersionType.MAJOR.equals(versionType) == true) + { + serialVersionNumber.majorIncrement(); + } + else + { + serialVersionNumber.minorIncrement(); + } + return serialVersionNumber.toString(); } @@ -133,7 +135,7 @@ public class SerialVersionLabelPolicy implements CalculateVersionLabelPolicy } else { - majorRevisionNumber = 1; + majorRevisionNumber = 0; minorRevisionNumber = 0; } } diff --git a/source/java/org/alfresco/repo/version/common/versionlabel/SerialVersionLabelPolicyTest.java b/source/java/org/alfresco/repo/version/common/versionlabel/SerialVersionLabelPolicyTest.java index 642e5be4ea..60f586a66e 100644 --- a/source/java/org/alfresco/repo/version/common/versionlabel/SerialVersionLabelPolicyTest.java +++ b/source/java/org/alfresco/repo/version/common/versionlabel/SerialVersionLabelPolicyTest.java @@ -55,7 +55,7 @@ public class SerialVersionLabelPolicyTest extends TestCase null, 0, versionProp1); - assertEquals("1.0", initialVersion); + assertEquals("Minor initial version not 0.1", "0.1", initialVersion); HashMap versionProp2 = new HashMap(); versionProp2.put(VersionModel.PROP_VERSION_LABEL, "1.0"); @@ -66,7 +66,7 @@ public class SerialVersionLabelPolicyTest extends TestCase version1, 1, versionProp1); - assertEquals("1.1", verisonLabel1); + assertEquals("Minor update from 1.0 not correct", "1.1", verisonLabel1); HashMap versionProp3 = new HashMap(); versionProp3.put(VersionModel.PROP_VERSION_LABEL, "1.1"); @@ -80,7 +80,7 @@ public class SerialVersionLabelPolicyTest extends TestCase version2, 1, versionProp4); - assertEquals("2.0", verisonLabel2); + assertEquals("major version update not correct", "2.0", verisonLabel2); } } diff --git a/source/java/org/alfresco/repo/workflow/AbstractWorkflowServiceIntegrationTest.java b/source/java/org/alfresco/repo/workflow/AbstractWorkflowServiceIntegrationTest.java index a8c3b0b40f..e2812c8e88 100644 --- a/source/java/org/alfresco/repo/workflow/AbstractWorkflowServiceIntegrationTest.java +++ b/source/java/org/alfresco/repo/workflow/AbstractWorkflowServiceIntegrationTest.java @@ -22,6 +22,7 @@ package org.alfresco.repo.workflow; import java.io.InputStream; import java.io.Serializable; import java.util.Arrays; +import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -381,7 +382,12 @@ public abstract class AbstractWorkflowServiceIntegrationTest extends BaseSpringT assertEquals(1, tasks.size()); WorkflowTask currentTask = tasks.get(0); assertEquals(currentTask.getState(), WorkflowTaskState.IN_PROGRESS); - assertNull(currentTask.getProperties().get(ContentModel.PROP_OWNER)); + Map taskProperties = currentTask.getProperties(); + assertNull(taskProperties.get(ContentModel.PROP_OWNER)); + + Serializable pooledActors = taskProperties.get(WorkflowModel.ASSOC_POOLED_ACTORS); + assertNotNull(pooledActors); + assertTrue(((Collection)pooledActors).contains(group)); // ensure the task is not reassignable by any user assertFalse(workflowService.isTaskReassignable(currentTask, USER1)); @@ -419,6 +425,74 @@ public abstract class AbstractWorkflowServiceIntegrationTest extends BaseSpringT assertFalse(workflowService.isTaskClaimable(currentTask, USER2)); assertFalse(workflowService.isTaskEditable(currentTask, USER2)); + // Release the task + properties.clear(); + properties.put(ContentModel.PROP_OWNER, null); + workflowService.updateTask(currentTask.getId(), properties, null, null); + currentTask = workflowService.getTaskById(currentTask.getId()); + assertTrue(workflowService.isTaskClaimable(currentTask, USER1)); + + // Set the Pooled actors to USer2 and User3 + properties.clear(); + NodeRef person2 = personManager.get(USER2); + NodeRef person3 = personManager.get(USER3); + List actors = Arrays.asList(person2, person3); + properties.put(WorkflowModel.ASSOC_POOLED_ACTORS, (Serializable) actors); + currentTask = workflowService.updateTask(currentTask.getId(), properties, null, null); + taskProperties = currentTask.getProperties(); + Collection newActors = (Collection) taskProperties.get(WorkflowModel.ASSOC_POOLED_ACTORS); + assertEquals(2, newActors.size()); + assertTrue(newActors.contains(person2)); + assertTrue(newActors.contains(person3)); + + // ensure the task is not reassignable by any user + assertFalse(workflowService.isTaskReassignable(currentTask, USER1)); + assertFalse(workflowService.isTaskReassignable(currentTask, USER2)); + assertFalse(workflowService.isTaskReassignable(currentTask, USER3)); + + // ensure the task is not releasable by any user + assertFalse(workflowService.isTaskReleasable(currentTask, USER1)); + assertFalse(workflowService.isTaskReleasable(currentTask, USER2)); + assertFalse(workflowService.isTaskReleasable(currentTask, USER3)); + + // ensure the task is claimable by the pooled actors + assertTrue(workflowService.isTaskClaimable(currentTask, USER2)); + assertTrue(workflowService.isTaskClaimable(currentTask, USER3)); + + // ensure the task is not claimable by users who are not pooled actors + assertFalse(workflowService.isTaskClaimable(currentTask, USER1)); + + // ensure the task can be edited + assertFalse(workflowService.isTaskEditable(currentTask, USER1)); + assertTrue(workflowService.isTaskEditable(currentTask, USER2)); + assertTrue(workflowService.isTaskEditable(currentTask, USER3)); + + // Claim task for User3 + properties.clear(); + properties.put(ContentModel.PROP_OWNER, USER3); + currentTask = workflowService.updateTask(currentTask.getId(), properties, null, null); + taskProperties = currentTask.getProperties(); + + // Check if task is claimable + assertFalse(workflowService.isTaskClaimable(currentTask, USER1)); + assertFalse(workflowService.isTaskClaimable(currentTask, USER2)); + assertFalse(workflowService.isTaskClaimable(currentTask, USER3)); + + // Check if task is releasable + assertFalse(workflowService.isTaskReleasable(currentTask, USER1)); + assertFalse(workflowService.isTaskReleasable(currentTask, USER2)); + assertTrue(workflowService.isTaskReleasable(currentTask, USER3)); + + // Check if task is Editable + assertFalse(workflowService.isTaskEditable(currentTask, USER1)); + assertFalse(workflowService.isTaskEditable(currentTask, USER2)); + assertTrue(workflowService.isTaskEditable(currentTask, USER3)); + + // Check task cannot be Reassignable + assertFalse(workflowService.isTaskReassignable(currentTask, USER1)); + assertFalse(workflowService.isTaskReassignable(currentTask, USER2)); + assertFalse(workflowService.isTaskReassignable(currentTask, USER3)); + // cancel the workflow workflowService.cancelWorkflow(workflowInstanceId); } diff --git a/source/java/org/alfresco/repo/workflow/PackageManager.java b/source/java/org/alfresco/repo/workflow/PackageManager.java index 6f8029fd4b..0e1ab87619 100644 --- a/source/java/org/alfresco/repo/workflow/PackageManager.java +++ b/source/java/org/alfresco/repo/workflow/PackageManager.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Set; import org.alfresco.model.ContentModel; +import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.service.cmr.repository.ChildAssociationRef; @@ -65,16 +66,19 @@ public class PackageManager private final WorkflowService workflowService; private final NodeService nodeService; private final Log logger; + private final BehaviourFilter behaviourFilter; private final Set addItems = new HashSet(); private final Set removeItems = new HashSet(); public PackageManager(WorkflowService workflowService, NodeService nodeService, + BehaviourFilter behaviourFilter, Log logger) { this.workflowService = workflowService; this.nodeService = nodeService; + this.behaviourFilter =behaviourFilter; this.logger = logger ==null ? LOGGER : logger; } @@ -207,19 +211,28 @@ public class PackageManager } } - private void addPackageItems(final NodeRef packageRef) { for (NodeRef item : addItems) - { - String name = - (String) nodeService.getProperty(item, ContentModel.PROP_NAME); - if(name == null) - name = GUID.generate(); - String localName = QName.createValidLocalName(name); - QName qName = QName.createQName(CM_URL, localName); - nodeService.addChild(packageRef, item, PCKG_CONTAINS, qName); - } + { + String name = (String) nodeService.getProperty(item, ContentModel.PROP_NAME); + if (name == null) + { + name = GUID.generate(); + } + String localName = QName.createValidLocalName(name); + QName qName = QName.createQName(CM_URL, localName); + + behaviourFilter.disableBehaviour(item, ContentModel.ASPECT_AUDITABLE); + try + { + nodeService.addChild(packageRef, item, PCKG_CONTAINS, qName); + } + finally + { + behaviourFilter.enableBehaviour(item, ContentModel.ASPECT_AUDITABLE); + } + } } private List getCurrentItems(NodeRef packageRef) diff --git a/source/java/org/alfresco/repo/workflow/TaskUpdater.java b/source/java/org/alfresco/repo/workflow/TaskUpdater.java index 3f7796818a..4daf2ebcae 100644 --- a/source/java/org/alfresco/repo/workflow/TaskUpdater.java +++ b/source/java/org/alfresco/repo/workflow/TaskUpdater.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.workflow.WorkflowService; @@ -56,11 +57,12 @@ public class TaskUpdater public TaskUpdater(String taskId, WorkflowService workflowService, - NodeService nodeService) + NodeService nodeService, + BehaviourFilter behaviourFilter) { this.taskId = taskId; this.workflowService = workflowService; - this.packageMgr = new PackageManager(workflowService, nodeService, LOGGER); + this.packageMgr = new PackageManager(workflowService, nodeService, behaviourFilter, LOGGER); } diff --git a/source/java/org/alfresco/repo/workflow/WorkflowBuilder.java b/source/java/org/alfresco/repo/workflow/WorkflowBuilder.java index c5498700ac..2a77ad65b0 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowBuilder.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowBuilder.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.workflow.WorkflowDefinition; @@ -51,10 +52,13 @@ public class WorkflowBuilder private final Map params = new HashMap(); private NodeRef packageNode = null; - public WorkflowBuilder(WorkflowDefinition definition, WorkflowService workflowService, NodeService nodeService) + public WorkflowBuilder(WorkflowDefinition definition, + WorkflowService workflowService, + NodeService nodeService, + BehaviourFilter behaviourFilter) { this.workflowService = workflowService; - this.packageMgr = new PackageManager(workflowService, nodeService, null); + this.packageMgr = new PackageManager(workflowService, nodeService, behaviourFilter, null); this.definition = definition; } diff --git a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java index df9a4481a8..96dc6e0272 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java @@ -1000,32 +1000,48 @@ public class WorkflowServiceImpl implements WorkflowService * @param username The username to check * @return true if the user is a pooled actor, false otherwise */ + @SuppressWarnings("unchecked") private boolean isUserInPooledActors(WorkflowTask task, String username) { - // get groups that the current user has to belong (at least one of them) - final Collection actors = (Collection)task.getProperties().get(WorkflowModel.ASSOC_POOLED_ACTORS); - if (actors != null && !actors.isEmpty()) + // Get the pooled actors + Collection actors = (Collection)task.getProperties().get(WorkflowModel.ASSOC_POOLED_ACTORS); + if (actors != null) { - for (Object actor : actors) + for (NodeRef actor : actors) { - // retrieve the name of the group - Map props = nodeService.getProperties((NodeRef)actor); - String name = (String)props.get(ContentModel.PROP_AUTHORITY_NAME); - - // retrieve the users of the group - Set users = this.authorityService.getContainedAuthorities(AuthorityType.USER, name, false); - - // see if the user is one of the users in the group - if (users != null && !users.isEmpty() && users.contains(username)) + QName type = nodeService.getType(actor); + if (dictionaryService.isSubClass(type, ContentModel.TYPE_PERSON)) { - // they are a member of the group so stop looking! - return true; + Serializable name = nodeService.getProperty(actor, ContentModel.PROP_USERNAME); + if(name!=null && name.equals(username)) + { + return true; + } + } + else if (dictionaryService.isSubClass(type, ContentModel.TYPE_AUTHORITY_CONTAINER)) + { + if (isUserInGroup(username, actor)) + { + // The user is a member of the group + return true; + } } } } - return false; } + + private boolean isUserInGroup(String username, NodeRef group) + { + // Get the group name + String name = (String)nodeService.getProperty(group, ContentModel.PROP_AUTHORITY_NAME); + + // Get all group members + Set groupMembers = authorityService.getContainedAuthorities(AuthorityType.USER, name, false); + + // Chekc if the user is a group member. + return groupMembers != null && groupMembers.contains(username); + } /** * Determines if the given user is the owner of the given task or diff --git a/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoExecuteNodeJob.java b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoExecuteNodeJob.java new file mode 100644 index 0000000000..ae8c68dbde --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoExecuteNodeJob.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.jbpm; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.jbpm.JbpmContext; +import org.jbpm.graph.exe.Token; +import org.jbpm.job.ExecuteNodeJob; +import org.jbpm.taskmgmt.exe.TaskInstance; + +/** + * @since 3.4 + * @author Nick Smith + * + */ +public class AlfrescoExecuteNodeJob extends ExecuteNodeJob +{ + private static final long serialVersionUID = 6257575556379132535L; + + public AlfrescoExecuteNodeJob() + { + super(); + } + + public AlfrescoExecuteNodeJob(Token token) + { + super(token); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean execute(final JbpmContext jbpmContext) throws Exception + { + // establish authentication context + final TaskInstance taskInstance = getTaskInstance(); + String username = getActorId(taskInstance); + + // execute timer + return AuthenticationUtil.runAs(new RunAsWork() + { + @Override + public Boolean doWork() throws Exception + { + return AlfrescoExecuteNodeJob.super.execute(jbpmContext); + } + }, username); + } + + private String getActorId(TaskInstance taskInstance) + { + if (taskInstance != null) + { + String actorId = taskInstance.getActorId(); + if (actorId != null && actorId.length() > 0) + { + return actorId; + } + } + return AuthenticationUtil.getSystemUserName(); + } +} diff --git a/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoTaskNode.java b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoTaskNode.java new file mode 100644 index 0000000000..28f79e4505 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoTaskNode.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.workflow.jbpm; + +import java.util.Date; + +import org.jbpm.graph.def.Action; +import org.jbpm.graph.exe.Token; +import org.jbpm.graph.node.TaskNode; +import org.jbpm.job.ExecuteActionJob; +import org.jbpm.job.ExecuteNodeJob; + +/** + * @since 3.4 + * @author Nick Smith + * + */ +public class AlfrescoTaskNode extends TaskNode +{ + private static final long serialVersionUID = -5582345187516764993L; + + public AlfrescoTaskNode() + { + super(); + } + + public AlfrescoTaskNode(String name) + { + super(name); + } + +// /** +// * {@inheritDoc} +// */ +// @Override +// protected ExecuteNodeJob createAsyncContinuationJob(Token token) +// { +// AlfrescoExecuteNodeJob job = new AlfrescoExecuteNodeJob(token); +// job.setNode(this); +// job.setDueDate(new Date()); +// job.setExclusive(isAsyncExclusive); +// return job; +// } + +} diff --git a/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoTimer.java b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoTimer.java index 1dfa90196a..6f2a9f71b1 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoTimer.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoTimer.java @@ -33,6 +33,7 @@ import org.jbpm.taskmgmt.exe.TaskInstance; * task, the timer is executed unauthenticated. * * @author davidc + * @author Nick Smith */ public class AlfrescoTimer extends Timer { @@ -56,35 +57,23 @@ public class AlfrescoTimer extends Timer super(token); } - /* (non-Javadoc) - * @see org.jbpm.job.Job#execute(org.jbpm.JbpmContext) + /** + * {@inheritDoc} */ @Override public boolean execute(final JbpmContext jbpmContext) throws Exception { - boolean executeResult = false; - // establish authentication context - String username = null; final TaskInstance taskInstance = getTaskInstance(); - if (taskInstance != null) - { - String actorId = taskInstance.getActorId(); - if (actorId != null && actorId.length() > 0) - { - username = actorId; - } - } + String username = getActorId(taskInstance); // execute timer - executeResult = AuthenticationUtil.runAs(new RunAsWork() + return AuthenticationUtil.runAs(new RunAsWork() { - @SuppressWarnings("synthetic-access") public Boolean doWork() throws Exception { boolean deleteTimer = AlfrescoTimer.super.execute(jbpmContext); - // End the task if timer does not repeat. // Note the order is a little odd here as the task will be ended // after the token has been signalled to move to the next node. @@ -97,9 +86,20 @@ public class AlfrescoTimer extends Timer } return deleteTimer; } - }, (username == null) ? "system" : username); - - return executeResult; + }, username); + } + + private String getActorId(TaskInstance taskInstance) + { + if (taskInstance != null) + { + String actorId = taskInstance.getActorId(); + if (actorId != null && actorId.length() > 0) + { + return actorId; + } + } + return AuthenticationUtil.getSystemUserName(); } } diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JbpmWorkflowServiceIntegrationTest.java b/source/java/org/alfresco/repo/workflow/jbpm/JbpmWorkflowServiceIntegrationTest.java index 64f922f6f5..43032fed85 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JbpmWorkflowServiceIntegrationTest.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JbpmWorkflowServiceIntegrationTest.java @@ -18,7 +18,19 @@ */ package org.alfresco.repo.workflow.jbpm; +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.alfresco.repo.workflow.AbstractWorkflowServiceIntegrationTest; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskState; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; @@ -31,6 +43,72 @@ import org.alfresco.service.namespace.QName; public class JbpmWorkflowServiceIntegrationTest extends AbstractWorkflowServiceIntegrationTest { + @SuppressWarnings("deprecation") + public void disabledTestAsynchronousTaskExecutes() throws Exception + { + setComplete(); + endTransaction(); + + String defId = null; + String instanceId = null; + try + { + WorkflowDefinition def = deployDefinition(getAsyncAdhocPath()); + defId = def.getId(); + + // Create workflow parameters + Map params = new HashMap(); + Serializable wfPackage = workflowService.createPackage(null); + params.put(WorkflowModel.ASSOC_PACKAGE, wfPackage); + Date dueDate = new Date(); + params.put(WorkflowModel.PROP_WORKFLOW_DUE_DATE, dueDate); + params.put(WorkflowModel.PROP_WORKFLOW_PRIORITY, 1); + NodeRef assignee = personManager.get(USER2); + params.put(WorkflowModel.ASSOC_ASSIGNEE, assignee); + + WorkflowPath path = workflowService.startWorkflow(defId, params); + instanceId = path.getInstance().getId(); + + // End the Start Task. + List tasks = workflowService.getTasksForWorkflowPath(path.getId()); + assertEquals(1, tasks.size()); + WorkflowTask startTask = tasks.get(0); + workflowService.endTask(startTask.getId(), null); + + // Wait for async execution to occur. + Thread.sleep(1000); + + // Should move past the asynchronous adhoc task. + tasks = workflowService.getTasksForWorkflowPath(path.getId()); + assertEquals(1, tasks.size()); + WorkflowTask endTask = tasks.get(0); + assertEquals("wf:completedAdhocTask", endTask.getName()); + + // Check async task assigned to USER2 + tasks = workflowService.getAssignedTasks(USER2, WorkflowTaskState.IN_PROGRESS); + assertEquals(1, tasks.size()); + WorkflowTask adhocTask = tasks.get(0); + assertEquals("wf:adhocTask", adhocTask.getName()); + } + finally + { + if(instanceId != null) + { + workflowService.cancelWorkflow(instanceId); + } + if(defId != null) + { + workflowService.undeployDefinition(defId); + } + + } + } + + private String getAsyncAdhocPath() + { + return "jbpmresources/async_adhoc_processdefinition.xml"; + } + @Override protected String getEngine() { diff --git a/source/java/org/alfresco/repo/workflow/jbpm/jbpm.ExecuteNodeJob.hbm.xml b/source/java/org/alfresco/repo/workflow/jbpm/jbpm.ExecuteNodeJob.hbm.xml new file mode 100644 index 0000000000..7dfc6274cb --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/jbpm.ExecuteNodeJob.hbm.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/source/java/org/alfresco/repo/workflow/jbpm/jbpm.TaskNode.hbm.xml b/source/java/org/alfresco/repo/workflow/jbpm/jbpm.TaskNode.hbm.xml new file mode 100644 index 0000000000..3c8f247f3a --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/jbpm.TaskNode.hbm.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/source/java/org/alfresco/service/cmr/action/ExecutionSummary.java b/source/java/org/alfresco/service/cmr/action/ExecutionSummary.java index a2444dbd9f..dc83c3c9a1 100644 --- a/source/java/org/alfresco/service/cmr/action/ExecutionSummary.java +++ b/source/java/org/alfresco/service/cmr/action/ExecutionSummary.java @@ -66,4 +66,8 @@ public class ExecutionSummary { public int getExecutionInstance() { return executionInstance; } + + public String toString() { + return "Execution of " + actionType + " as " + executionInstance + " : " + actionId; + } } \ No newline at end of file diff --git a/source/java/org/alfresco/service/cmr/site/SiteService.java b/source/java/org/alfresco/service/cmr/site/SiteService.java index 6c3ef11b85..9af85bbdd1 100644 --- a/source/java/org/alfresco/service/cmr/site/SiteService.java +++ b/source/java/org/alfresco/service/cmr/site/SiteService.java @@ -250,13 +250,24 @@ public interface SiteService boolean hasContainer(String shortName, String componentId); /** - * Gets a list of all the currently available roles that a user can perform on a site + * Gets a list of all the currently available roles that a user can perform on + * all sites * * @return List list of available roles */ @NotAuditable List getSiteRoles(); + /** + * Gets a list of all the currently available roles that a user can perform on + * a specific site. This will generally only differ from {@link #getSiteRoles()} + * if your site is of a custom type. + * + * @return List list of available roles + */ + @NotAuditable + List getSiteRoles(String shortName); + /** * Gets the sites group. All members of the site are contained within this group. * @@ -267,7 +278,7 @@ public interface SiteService String getSiteGroup(String shortName); /** - * Gets the sites role group. All members assigned the given role will be memebers of + * Gets the sites role group. All members assigned the given role will be members of * the returned group. * * @param shortName site short name diff --git a/source/java/org/alfresco/service/cmr/usage/UsageService.java b/source/java/org/alfresco/service/cmr/usage/UsageService.java index 2e26e66933..f1d8f6f6dd 100644 --- a/source/java/org/alfresco/service/cmr/usage/UsageService.java +++ b/source/java/org/alfresco/service/cmr/usage/UsageService.java @@ -50,8 +50,7 @@ public interface UsageService */ @NotAuditable public long getAndRemoveTotalDeltaSize(NodeRef usageNodeRef); - - /** + /** * Get distinct set of usage delta nodes */ diff --git a/source/java/org/alfresco/util/schemadump/Main.java b/source/java/org/alfresco/util/schemadump/Main.java index 243e0c8278..b4306f6bb7 100644 --- a/source/java/org/alfresco/util/schemadump/Main.java +++ b/source/java/org/alfresco/util/schemadump/Main.java @@ -42,6 +42,7 @@ import javax.xml.transform.sax.TransformerHandler; import javax.xml.transform.stream.StreamResult; import org.alfresco.util.PropertyCheck; +import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.dialect.TypeNames; @@ -130,7 +131,7 @@ public class Main @SuppressWarnings("unchecked") private void init(final Dialect dialect) throws Exception { - this.scaleCharacters = dialect instanceof Oracle8iDialect; + this.scaleCharacters = (dialect instanceof Oracle8iDialect) || (dialect instanceof DB2Dialect); final Field typeNamesField = Dialect.class.getDeclaredField("typeNames"); typeNamesField.setAccessible(true); final TypeNames typeNames = (TypeNames) typeNamesField.get(dialect); diff --git a/source/java/org/alfresco/wcm/asset/AssetServiceImpl.java b/source/java/org/alfresco/wcm/asset/AssetServiceImpl.java index d2b95c99f1..524a654c23 100644 --- a/source/java/org/alfresco/wcm/asset/AssetServiceImpl.java +++ b/source/java/org/alfresco/wcm/asset/AssetServiceImpl.java @@ -55,7 +55,7 @@ import org.alfresco.service.namespace.QName; import org.alfresco.util.TempFileProvider; import org.alfresco.wcm.sandbox.SandboxConstants; import org.alfresco.wcm.util.WCMUtil; -import org.apache.tools.zip.ZipFile; +import org.apache.commons.compress.archivers.zip.ZipFile; import org.springframework.extensions.surf.util.ParameterCheck; /** @@ -589,7 +589,8 @@ public class AssetServiceImpl implements AssetService { // NOTE: This encoding allows us to workaround bug: // http://bugs.sun.com/bugdatabase/view_bug.do;:WuuT?bug_id=4820807 - ZipFile zipFile = new ZipFile(file, isHighByteZip ? "Cp437" : null); + // We also try to use the extra encoding information if present + ZipFile zipFile = new ZipFile(file, isHighByteZip ? "Cp437" : null, true); File alfTempDir = TempFileProvider.getTempDir(); // build a temp dir name based on the name of the file we are importing File tempDir = new File(alfTempDir.getPath() + File.separatorChar + file.getName() + "_unpack"); diff --git a/source/java/org/alfresco/wcm/webproject/WebProjectServiceImpl.java b/source/java/org/alfresco/wcm/webproject/WebProjectServiceImpl.java index 62a9e076d1..02a66f1705 100644 --- a/source/java/org/alfresco/wcm/webproject/WebProjectServiceImpl.java +++ b/source/java/org/alfresco/wcm/webproject/WebProjectServiceImpl.java @@ -91,9 +91,11 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService /** The web projects root node reference */ private NodeRef webProjectsRootNodeRef; // note: WCM is not currently MT-enabled (so this is OK) + private boolean isSetWebProjectsRootNodeRef; /** Services */ private NodeService nodeService; + private NamespaceService namespaceService; private SearchService searchService; private AVMService avmService; private AuthorityService authorityService; @@ -109,7 +111,12 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService { this.nodeService = nodeService; } - + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + public void setSearchService(SearchService searchService) { this.searchService = searchService; @@ -453,31 +460,30 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService */ public boolean hasWebProjectsRoot() { - boolean hasRoot = false; - - // Get the root 'web projects' folder - ResultSet resultSet = null; - try + return getWebProjectsRootOrNull() != null; + } + + private NodeRef getWebProjectsRootOrNull() + { + if (!this.isSetWebProjectsRootNodeRef) { - resultSet = this.searchService.query(WEBPROJECT_STORE, SearchService.LANGUAGE_LUCENE, "PATH:\""+getWebProjectsPath()+"\""); - if (resultSet.length() == 1) + // Get the root 'web projects' folder + List results = this.searchService.selectNodes(this.nodeService.getRootNode(WEBPROJECT_STORE), + getWebProjectsPath(), null, this.namespaceService, false); + int size = results.size(); + if (size > 1) { - hasRoot = true; + // More than one root web projects folder exits + throw new AlfrescoRuntimeException("More than one root 'Web Projects' folder exists"); } - else if (resultSet.length() > 1 && logger.isWarnEnabled()) + if (size > 0) { - logger.warn("More than one root 'Web Projects' folder exists"); + this.webProjectsRootNodeRef = results.get(0); } + this.isSetWebProjectsRootNodeRef = true; } - finally - { - if (resultSet != null) - { - resultSet.close(); - } - } - return hasRoot; + return this.webProjectsRootNodeRef; } /** @@ -487,35 +493,13 @@ public class WebProjectServiceImpl extends WCMUtil implements WebProjectService */ public NodeRef getWebProjectsRoot() { - if (this.webProjectsRootNodeRef == null) + NodeRef result = getWebProjectsRootOrNull(); + if (result == null) { - // Get the root 'web projects' folder - ResultSet resultSet = null; - try - { - resultSet = this.searchService.query(WEBPROJECT_STORE, SearchService.LANGUAGE_LUCENE, "PATH:\""+getWebProjectsPath()+"\""); - if (resultSet.length() == 0) - { - // No root web projects folder exists - throw new AlfrescoRuntimeException("No root 'Web Projects' folder exists (is WCM enabled ?)"); - } - else if (resultSet.length() != 1) - { - // More than one root web projects folder exits - throw new AlfrescoRuntimeException("More than one root 'Web Projects' folder exists"); - } - this.webProjectsRootNodeRef = resultSet.getNodeRef(0); - } - finally - { - if (resultSet != null) - { - resultSet.close(); - } - } + // No root web projects folder exists + throw new AlfrescoRuntimeException("No root 'Web Projects' folder exists (is WCM enabled ?)"); } - - return this.webProjectsRootNodeRef; + return result; } /* (non-Javadoc) diff --git a/source/test-resources/filesys/ContentDiskDriverTest1.docx b/source/test-resources/filesys/ContentDiskDriverTest1.docx new file mode 100644 index 0000000000..45189553dc Binary files /dev/null and b/source/test-resources/filesys/ContentDiskDriverTest1.docx differ diff --git a/source/test-resources/filesys/ContentDiskDriverTest2.docx b/source/test-resources/filesys/ContentDiskDriverTest2.docx new file mode 100644 index 0000000000..daf6c1617e Binary files /dev/null and b/source/test-resources/filesys/ContentDiskDriverTest2.docx differ diff --git a/source/test-resources/jbpmresources/async_adhoc_processdefinition.xml b/source/test-resources/jbpmresources/async_adhoc_processdefinition.xml new file mode 100644 index 0000000000..6ba0c845c9 --- /dev/null +++ b/source/test-resources/jbpmresources/async_adhoc_processdefinition.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + #{bpm_assignee} + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/test-resources/quick/quick.chinese.msg b/source/test-resources/quick/quick.chinese.msg new file mode 100644 index 0000000000..a88555e93f Binary files /dev/null and b/source/test-resources/quick/quick.chinese.msg differ